mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: profile page layout & fix html layout
This commit is contained in:
parent
960bec4d5e
commit
846d96473f
@ -5,39 +5,14 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<meta name="description" content="Learn management system" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Application</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import { Flex, Box, useColorMode } from '@chakra-ui/react';
|
||||
import AppName from './AppName';
|
||||
import UserInfo from './User/UserInfo';
|
||||
import UserNotifications from './User/UserNotifications';
|
||||
@ -11,6 +11,7 @@ const Header: React.FC = () => {
|
||||
const {
|
||||
uiStore: { isDesktop },
|
||||
} = useRootStore();
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -20,8 +21,7 @@ const Header: React.FC = () => {
|
||||
left={0}
|
||||
right={0}
|
||||
borderBottomWidth={1}
|
||||
width='100%'
|
||||
height='4rem'
|
||||
backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}
|
||||
zIndex={4}
|
||||
>
|
||||
<Flex
|
||||
@ -29,8 +29,7 @@ const Header: React.FC = () => {
|
||||
width='100%'
|
||||
height='100%'
|
||||
alignItems='center'
|
||||
paddingLeft='1.5rem'
|
||||
paddingRight='1.5rem'
|
||||
padding='5px 1.5rem 5px 1.5rem'
|
||||
>
|
||||
<AppName />
|
||||
<Flex>
|
||||
|
||||
@ -24,16 +24,6 @@ const UserInfo: React.FC = () => {
|
||||
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);
|
||||
// }
|
||||
// };
|
||||
|
||||
if (userStore.me === null) {
|
||||
return <Skeleton width='260px' height='40px' borderRadius='md' />;
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import { observer } from 'mobx-react';
|
||||
import { useRootStore } from 'hooks/useRootStore';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { ColorModeSwitcher } from 'components/ColorModeSwitcher/ColorModeSwitcher';
|
||||
import LoadingPage from 'components/Loading/LoadingPage';
|
||||
|
||||
/**
|
||||
*
|
||||
@ -24,24 +23,29 @@ const Layout: React.FC = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Flex minH='100vh'>
|
||||
<NavDesktop />
|
||||
<Flex minH='100vh' flexDirection='column'>
|
||||
<Box
|
||||
pl='80'
|
||||
mt='20'
|
||||
mb='10'
|
||||
as='main'
|
||||
flex='1 0 auto'
|
||||
maxW='85rem'
|
||||
ml='25rem'
|
||||
mt='7rem'
|
||||
mb='1rem'
|
||||
pr='2rem'
|
||||
pl='2rem'
|
||||
sx={{
|
||||
'@media (max-width: 768px)': {
|
||||
paddingLeft: 0,
|
||||
'@media (max-width: 1280px)': {
|
||||
ml: '20rem',
|
||||
},
|
||||
'@media (max-width: 991px)': {
|
||||
ml: '0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box as='main' mr='auto' ml='auto' maxW='85rem' pl={5} pr={5} pt='2rem' pb='2rem'>
|
||||
{userStore.me === null ? <LoadingPage /> : children}
|
||||
{children}
|
||||
</Box>
|
||||
<ColorModeSwitcher position='absolute' right={0} bottom={0} margin={5} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
||||
24
src/components/Loading/LoadingProfilePage.tsx
Normal file
24
src/components/Loading/LoadingProfilePage.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { Box, Skeleton, SkeletonCircle, Flex, Spinner } from '@chakra-ui/react';
|
||||
|
||||
const LoadingProfilePage = () => {
|
||||
return (
|
||||
<Box maxW='900px'>
|
||||
<Skeleton height='35px' width='250px' borderRadius='md' />
|
||||
<Flex alignItems='center' mt='20'>
|
||||
<Box>
|
||||
<SkeletonCircle size='128px' />
|
||||
</Box>
|
||||
<Flex ml='50' flexDirection='column'>
|
||||
<Skeleton height='25px' width='200px' mb='5' borderRadius='md' />
|
||||
<Skeleton height='25px' width='300px' borderRadius='md' />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex mt='20' justifyContent='center'>
|
||||
<Spinner size='xl' />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingProfilePage;
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import { Flex, Box, useColorMode } from '@chakra-ui/react';
|
||||
import NavList from '../NavList';
|
||||
import { useRootStore } from 'hooks/useRootStore';
|
||||
import { observer } from 'mobx-react';
|
||||
@ -14,23 +14,34 @@ const Nav: React.FC = () => {
|
||||
const {
|
||||
uiStore: { isDesktop },
|
||||
} = useRootStore();
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
if (isDesktop) {
|
||||
return (
|
||||
<Box display='block' position='fixed' left={0} right={0} top={0} width='100%' height='100%' maxW='18rem'>
|
||||
<Box top='4rem' position='relative' overflowY='auto' borderRightWidth={1}>
|
||||
<Box
|
||||
borderRightWidth='1px'
|
||||
w='18rem'
|
||||
position='fixed'
|
||||
bottom='0'
|
||||
top='0'
|
||||
left='0'
|
||||
overflowY='auto'
|
||||
zIndex='1'
|
||||
backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}
|
||||
>
|
||||
<Flex
|
||||
as='nav'
|
||||
flexDirection='column'
|
||||
alignItems='flex-start'
|
||||
textDecoration='none'
|
||||
height='calc(100vh - 4rem)'
|
||||
padding='5'
|
||||
pt='4rem'
|
||||
pr='5'
|
||||
pl='5'
|
||||
pb='5'
|
||||
>
|
||||
<NavList links={config.links} />
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@ -11,11 +11,13 @@ import { HamburgerIcon } from '@chakra-ui/icons';
|
||||
import { useDisclosure, IconButton, useColorMode, Skeleton } from '@chakra-ui/react';
|
||||
import NavList from '../NavList';
|
||||
import config from 'config';
|
||||
import useLogoutQuery from 'hooks/useLogoutQuery';
|
||||
|
||||
const NavMobile: React.FC = () => {
|
||||
const { userStore } = useRootStore();
|
||||
const { colorMode } = useColorMode();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { logout } = useLogoutQuery();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -24,19 +26,16 @@ const NavMobile: React.FC = () => {
|
||||
<DrawerOverlay />
|
||||
<DrawerContent backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}>
|
||||
<DrawerBody>
|
||||
<Flex as='nav' flexDirection='column' alignItems='flex-start' textDecoration='none'>
|
||||
<Flex flexDirection='column' height='100%'>
|
||||
<Flex as='nav' flexDirection='column' alignItems='flex-start' textDecoration='none' mb='20px'>
|
||||
<NavList links={config.links} onCloseDrawer={onClose} />
|
||||
</Flex>
|
||||
{userStore.me ? (
|
||||
<Box
|
||||
position='absolute'
|
||||
bottom={0}
|
||||
right={0}
|
||||
left={0}
|
||||
minHeight='60px'
|
||||
padding='10px'
|
||||
margin='5'
|
||||
borderRadius='md'
|
||||
marginTop='auto'
|
||||
backgroundColor={colorMode === 'dark' ? 'gray.700' : 'gray.100'}
|
||||
>
|
||||
<Flex alignItems='center'>
|
||||
@ -49,7 +48,13 @@ const NavMobile: React.FC = () => {
|
||||
View profile
|
||||
</Link>
|
||||
</Flex>
|
||||
<IconButton aria-label='Logout' backgroundColor='gray.200' marginLeft='auto' icon={<MdExitToApp />} />
|
||||
<IconButton
|
||||
onClick={logout}
|
||||
aria-label='Logout'
|
||||
backgroundColor={colorMode === 'dark' ? 'gray.600' : 'gray.200'}
|
||||
marginLeft='auto'
|
||||
icon={<MdExitToApp />}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
) : (
|
||||
@ -64,6 +69,7 @@ const NavMobile: React.FC = () => {
|
||||
margin='5'
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import NavLink from './NavLink';
|
||||
// import { useRootStore } from 'hooks/useRootStore';
|
||||
// import { userHasRoles } from 'helpers';
|
||||
// import { UserRoleEnum } from 'enums';
|
||||
import { useRootStore } from 'hooks/useRootStore';
|
||||
import { userHasRoles } from 'helpers';
|
||||
import { UserRoleEnum } from 'enums';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { LinkType } from 'interfaces';
|
||||
|
||||
@ -16,7 +16,7 @@ type NavListPropsType = {
|
||||
* Render a navigation list of links
|
||||
*/
|
||||
const NavList: React.FC<NavListPropsType> = ({ links, onCloseDrawer }) => {
|
||||
// const { userStore } = useRootStore();
|
||||
const { userStore } = useRootStore();
|
||||
const history = useHistory();
|
||||
const onClickLink = useCallback(
|
||||
(link: LinkType) => {
|
||||
@ -31,7 +31,14 @@ const NavList: React.FC<NavListPropsType> = ({ links, onCloseDrawer }) => {
|
||||
return (
|
||||
<>
|
||||
{links.map(link => {
|
||||
if (link.public) {
|
||||
return <NavLink link={link} key={link.location} onClickLink={onClickLink} />;
|
||||
} else {
|
||||
if (userStore.me && userHasRoles(userStore.me.roles, [UserRoleEnum.ADMIN])) {
|
||||
return <NavLink link={link} key={link.location} onClickLink={onClickLink} />;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,15 +1,39 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box, Heading, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Grid } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
Heading,
|
||||
Flex,
|
||||
Avatar,
|
||||
useColorMode,
|
||||
Text,
|
||||
Stack,
|
||||
Button,
|
||||
StackDivider,
|
||||
FormControl,
|
||||
Input,
|
||||
Tabs,
|
||||
TabList,
|
||||
Tab,
|
||||
TabPanels,
|
||||
TabPanel,
|
||||
FormHelperText,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
import { MdSave } from 'react-icons/md';
|
||||
|
||||
import { IPageProps } from 'interfaces';
|
||||
import { useRootStore } from 'hooks/useRootStore';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { observer } from 'mobx-react';
|
||||
import UserBadge from 'components/User/UserBadge';
|
||||
import LoadingProfilePage from 'components/Loading/LoadingProfilePage';
|
||||
|
||||
/**
|
||||
* Profile page
|
||||
*/
|
||||
const ProfilePage: React.FC<IPageProps> = ({ title }) => {
|
||||
const { authStore } = useRootStore();
|
||||
const { colorMode } = useColorMode();
|
||||
const { userStore, authStore } = useRootStore();
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
@ -18,23 +42,166 @@ const ProfilePage: React.FC<IPageProps> = ({ title }) => {
|
||||
}
|
||||
});
|
||||
|
||||
if (userStore.me === null) {
|
||||
return <LoadingProfilePage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Breadcrumb fontWeight='medium' fontSize='sm'>
|
||||
<BreadcrumbItem isCurrentPage>
|
||||
<BreadcrumbLink href='/'>{title}</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
<Box mt='10'>
|
||||
<Box maxW='900px'>
|
||||
<Heading as='h1'>My account</Heading>
|
||||
<Flex
|
||||
alignItems='center'
|
||||
mt='20'
|
||||
sx={{
|
||||
'@media (max-width: 768px)': {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
mt: '10',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Heading as='h1'>Profile page</Heading>
|
||||
<Grid templateColumns='3fr 1fr' mt='25px'>
|
||||
...
|
||||
</Grid>
|
||||
<Avatar
|
||||
border={`4px solid ${colorMode === 'dark' ? '#2D3748' : '#E2E8F0'}`}
|
||||
size='2xl'
|
||||
name={`${userStore.me.first_name} ${userStore.me.last_name}`}
|
||||
/>
|
||||
</Box>
|
||||
<Flex
|
||||
ml='50'
|
||||
flexDirection='column'
|
||||
sx={{
|
||||
'@media (max-width: 768px)': {
|
||||
ml: '0',
|
||||
mt: '5',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Heading as='h2'>{`${userStore.me.first_name} ${userStore.me.last_name}`}</Heading>
|
||||
<Flex
|
||||
alignItems='center'
|
||||
sx={{
|
||||
'@media (max-width: 768px)': {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
mr='10px'
|
||||
sx={{
|
||||
'@media (max-width: 768px)': {
|
||||
mr: '0',
|
||||
mb: '5px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{userStore.me.email}
|
||||
</Text>
|
||||
<UserBadge roles={userStore.me.roles} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Box mt='10'>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab>Account information</Tab>
|
||||
<Tab>My contacts</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{/* Account info */}
|
||||
<TabPanel padding='10px 0' mt='20px'>
|
||||
<Box borderRadius='md' borderWidth='1px' padding='20px'>
|
||||
<Heading as='h3' size='lg'>
|
||||
Account information
|
||||
</Heading>
|
||||
<Stack spacing='10px' mt='20px' divider={<StackDivider />}>
|
||||
<Flex>
|
||||
<Box>
|
||||
<Text as='small' color='gray.500'>
|
||||
First Name
|
||||
</Text>
|
||||
<Text fontWeight='medium' fontSize='lg'>
|
||||
{userStore.me.first_name}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Flex>
|
||||
<Box>
|
||||
<Text as='small' color='gray.500'>
|
||||
Last Name
|
||||
</Text>
|
||||
<Text fontWeight='medium' fontSize='lg'>
|
||||
{userStore.me.last_name}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Flex justifyContent='space-between' alignItems='center'>
|
||||
<Box>
|
||||
<Text as='small' color='gray.500'>
|
||||
Email
|
||||
</Text>
|
||||
<Text fontWeight='medium' fontSize='lg'>
|
||||
{userStore.me.email}
|
||||
</Text>
|
||||
</Box>
|
||||
<Button>Edit</Button>
|
||||
</Flex>
|
||||
|
||||
<Flex justifyContent='space-between' alignItems='center'>
|
||||
<Box>
|
||||
<Text as='small' color='gray.500'>
|
||||
Password
|
||||
</Text>
|
||||
<Text fontWeight='medium' fontSize='lg'>
|
||||
**********
|
||||
</Text>
|
||||
</Box>
|
||||
<Button>Change</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Box>
|
||||
</TabPanel>
|
||||
|
||||
{/* Contacts */}
|
||||
<TabPanel padding='10px 0' mt='20px'>
|
||||
<Box as='form'>
|
||||
<Box borderRadius='md' borderWidth='1px' padding='20px'>
|
||||
<Heading as='h3' size='lg'>
|
||||
My Contacts
|
||||
</Heading>
|
||||
<Stack spacing='5px' margin='20px 0'>
|
||||
<FormControl id='tel'>
|
||||
<FormHelperText color='gray.500'>Telephone</FormHelperText>
|
||||
<Input type='number' size='md' variant='flushed' placeholder='123-456-7890' />
|
||||
</FormControl>
|
||||
<FormControl id='twitter'>
|
||||
<FormHelperText color='gray.500'>Twitter</FormHelperText>
|
||||
<Input type='text' size='md' variant='flushed' placeholder='https://twitter.com/id' />
|
||||
</FormControl>
|
||||
<FormControl id='telegram'>
|
||||
<FormHelperText color='gray.500'>Telegram</FormHelperText>
|
||||
<Input type='text' size='md' variant='flushed' placeholder='@tag' />
|
||||
</FormControl>
|
||||
<FormControl id='vk'>
|
||||
<FormHelperText color='gray.500'>VKontakte</FormHelperText>
|
||||
<Input type='text' size='md' variant='flushed' placeholder='https://vk.com/id' />
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Button colorScheme='blue' mt='4' type='submit' size='md' variant='outline' rightIcon={<MdSave />}>
|
||||
Save
|
||||
</Button>
|
||||
</Box>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfilePage;
|
||||
export default observer(ProfilePage);
|
||||
|
||||
@ -54,11 +54,11 @@ export default class UIStore {
|
||||
}
|
||||
|
||||
get isDesktop() {
|
||||
return this.windowDimensions.width > 768;
|
||||
return this.windowDimensions.width > 991;
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this.windowDimensions.width < 768;
|
||||
return this.windowDimensions.width < 991;
|
||||
}
|
||||
|
||||
get isMobile() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user