mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: dark mode & public route
This commit is contained in:
parent
d4703b97d3
commit
d28cb97d30
15
src/App.tsx
15
src/App.tsx
@ -1,8 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { BrowserRouter as Router, Switch } from 'react-router-dom';
|
||||||
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
|
|
||||||
|
/**
|
||||||
|
* Components
|
||||||
|
*/
|
||||||
import PrivateRoute from './components/PrivateRoute/PrivateRoute';
|
import PrivateRoute from './components/PrivateRoute/PrivateRoute';
|
||||||
import ErrorFallback from './components/ErrorFallback';
|
import PublicRoute from './components/PublicRoute/PublicRoute';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pages
|
* Pages
|
||||||
@ -22,11 +25,11 @@ const App = () => {
|
|||||||
<PrivateRoute path='/courses' component={CoursesPage} title='Courses' />
|
<PrivateRoute path='/courses' component={CoursesPage} title='Courses' />
|
||||||
<PrivateRoute path='/messages' component={MessagesPage} title='Messages' />
|
<PrivateRoute path='/messages' component={MessagesPage} title='Messages' />
|
||||||
<PrivateRoute path='/profile' component={ProfilePage} title='Profile' />
|
<PrivateRoute path='/profile' component={ProfilePage} title='Profile' />
|
||||||
<Route path='/auth' component={AuthPage} />
|
<PublicRoute path='/auth' component={AuthPage} />
|
||||||
<Route component={NotFoundPage} />
|
<PublicRoute component={NotFoundPage} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default observer(App);
|
export default App;
|
||||||
|
|||||||
@ -6,10 +6,10 @@ const AppName: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Flex alignItems='center'>
|
<Flex alignItems='center'>
|
||||||
<Box mr='4'>
|
<Box mr='4'>
|
||||||
<MdSchool fill='blue' size='36px' />
|
<Box as={MdSchool} color='blue.500' size='36px' />
|
||||||
</Box>
|
</Box>
|
||||||
<Box lineHeight='1'>
|
<Box lineHeight='1'>
|
||||||
<Heading as='p' fontSize='2xl' color='gray.700'>
|
<Heading as='p' fontSize='2xl'>
|
||||||
App name
|
App name
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text as='small'>App description</Text>
|
<Text as='small'>App description</Text>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ const Header: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
as='header'
|
as='header'
|
||||||
background='#fff'
|
|
||||||
position='fixed'
|
position='fixed'
|
||||||
top={0}
|
top={0}
|
||||||
left={0}
|
left={0}
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import { MdNotifications } from 'react-icons/md';
|
|||||||
const UserNotifications: React.FC = () => {
|
const UserNotifications: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
marginRight={3}
|
mr={3}
|
||||||
|
ml={3}
|
||||||
variant='solid'
|
variant='solid'
|
||||||
outline='none'
|
outline='none'
|
||||||
border='none'
|
border='none'
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import Nav from '../Nav/Nav';
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { useRootStore } from '../../hooks/useRootStore';
|
import { useRootStore } from '../../hooks/useRootStore';
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
|
import { ColorModeSwitcher } from '../ColorModeSwitcher/ColorModeSwitcher';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -27,10 +28,12 @@ const Layout: React.FC = ({ children }) => {
|
|||||||
<>
|
<>
|
||||||
<Header />
|
<Header />
|
||||||
<Nav />
|
<Nav />
|
||||||
|
|
||||||
<Flex minH='100vh' flexDirection='column'>
|
<Flex minH='100vh' flexDirection='column'>
|
||||||
<Box pl='18rem' mt='4rem' mb='4rem' flex='1 0 auto'>
|
<Box pl='18rem' mt='4rem' mb='4rem' flex='1 0 auto'>
|
||||||
<Box as='main' mr='auto' ml='auto' maxW='85rem' pl={5} pr={5} pt='2rem' pb='2rem'>
|
<Box as='main' mr='auto' ml='auto' maxW='85rem' pl={5} pr={5} pt='2rem' pb='2rem'>
|
||||||
{children}
|
{children}
|
||||||
|
<ColorModeSwitcher position='absolute' right={0} bottom={0} margin={5} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const Nav: React.FC = () => {
|
|||||||
alignItems='flex-start'
|
alignItems='flex-start'
|
||||||
textDecoration='none'
|
textDecoration='none'
|
||||||
height='calc(100vh - 4rem)'
|
height='calc(100vh - 4rem)'
|
||||||
padding='1.5rem 1.2rem'
|
padding='5'
|
||||||
>
|
>
|
||||||
{config.links.map((link, i) => (
|
{config.links.map((link, i) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box, useColorMode } from '@chakra-ui/react';
|
||||||
import type { History } from 'history';
|
import type { History } from 'history';
|
||||||
import type { LinkType } from '../../interfaces';
|
import type { LinkType } from '../../interfaces';
|
||||||
import type { ActiveLinkState } from './Nav';
|
import type { ActiveLinkState } from './Nav';
|
||||||
@ -28,6 +28,7 @@ const NavLink: React.FC<NavLinkProps> = ({
|
|||||||
activeLink,
|
activeLink,
|
||||||
link,
|
link,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
const onMouseEnter = () => handleOnHoverLink(link.title);
|
const onMouseEnter = () => handleOnHoverLink(link.title);
|
||||||
const onClick = () => handleRoute(link.location);
|
const onClick = () => handleRoute(link.location);
|
||||||
|
|
||||||
@ -46,8 +47,8 @@ const NavLink: React.FC<NavLinkProps> = ({
|
|||||||
cursor='pointer'
|
cursor='pointer'
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
color={history.location.pathname === link.location || activeLink.title === link.title ? 'blue.500' : ''}
|
color={history.location.pathname === link.location || activeLink.title === link.title ? 'blue.400' : ''}
|
||||||
backgroundColor={activeLink.title === link.title ? '#EBF8FF' : ''}
|
backgroundColor={activeLink.title === link.title ? `${colorMode === 'dark' ? 'gray.700' : 'gray.100'}` : ''}
|
||||||
alignItems='center'
|
alignItems='center'
|
||||||
borderRadius='9999px'
|
borderRadius='9999px'
|
||||||
display='flex'
|
display='flex'
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import ErrorFallback from '../ErrorFallback';
|
|||||||
* Checks if the user is loggedIn by reactive variable and if not,
|
* Checks if the user is loggedIn by reactive variable and if not,
|
||||||
* then a redirect to "/auth" route.
|
* then a redirect to "/auth" route.
|
||||||
*/
|
*/
|
||||||
const PrivateRoute: React.FC<IPrivateRouteProps> = ({ children, component: Component, title, ...options }) => {
|
const PrivateRoute: React.FC<IPrivateRouteProps> = ({ component: Component, title, ...options }) => {
|
||||||
const { authStore } = useRootStore();
|
const { authStore } = useRootStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
23
src/components/PublicRoute/PublicRoute.tsx
Normal file
23
src/components/PublicRoute/PublicRoute.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
import { Route } from 'react-router-dom';
|
||||||
|
import { IPublicRouteProps } from '../../interfaces';
|
||||||
|
import ErrorFallback from '../ErrorFallback';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render public page
|
||||||
|
*/
|
||||||
|
const PublicRoute: React.FC<IPublicRouteProps> = ({ component: Component, title, ...options }) => {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
{...options}
|
||||||
|
render={props => (
|
||||||
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
|
<Component {...props} title={title} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PublicRoute;
|
||||||
@ -47,6 +47,11 @@ export interface IPageProps extends RouteComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IPrivateRouteProps extends RouteProps {
|
export interface IPrivateRouteProps extends RouteProps {
|
||||||
component: React.FC<any>;
|
component: React.FC<IPageProps>;
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPublicRouteProps extends RouteProps {
|
||||||
|
component: React.FC<IPageProps>;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,28 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import { Link } from 'react-router-dom';
|
import { MdError } from 'react-icons/md';
|
||||||
import { Box, Heading, Button } from '@chakra-ui/react';
|
import { Flex, Box, Heading, Text, useToast } from '@chakra-ui/react';
|
||||||
|
|
||||||
const NotFoundPage: React.FC = () => {
|
const NotFoundPage: React.FC = () => {
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
toast({ title: `Page not found`, duration: 10000, isClosable: true, status: 'warning' });
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Error 404</title>
|
<title>Error 404</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Box textAlign='center' mt={40}>
|
<Box textAlign='center' mt={40}>
|
||||||
|
<Flex justifyContent='center'>
|
||||||
|
<MdError size='64px' />
|
||||||
|
</Flex>
|
||||||
<Heading display='block' as='h1' fontSize='6xl'>
|
<Heading display='block' as='h1' fontSize='6xl'>
|
||||||
Error 404
|
Page not found
|
||||||
</Heading>
|
</Heading>
|
||||||
<Link to='/'>
|
<Text mt='5'>Sorry the page you are looking for could not be found.</Text>
|
||||||
<Button variant='link' colorScheme='blue'>
|
|
||||||
Return to main page
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user