feat: profile page layout & fix html layout

This commit is contained in:
Sergey Yarkov 2021-08-29 23:47:26 +03:00
parent 960bec4d5e
commit 846d96473f
10 changed files with 312 additions and 129 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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' />;
}

View File

@ -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 />
<NavDesktop />
<Flex minH='100vh' flexDirection='column'>
<Flex minH='100vh'>
<NavDesktop />
<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}
<ColorModeSwitcher position='absolute' right={0} bottom={0} margin={5} />
</Box>
{children}
</Box>
<ColorModeSwitcher position='absolute' right={0} bottom={0} margin={5} />
</Flex>
</>
);

View 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;

View File

@ -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,22 +14,33 @@ 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}>
<Flex
as='nav'
flexDirection='column'
alignItems='flex-start'
textDecoration='none'
height='calc(100vh - 4rem)'
padding='5'
>
<NavList links={config.links} />
</Flex>
</Box>
<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'
pt='4rem'
pr='5'
pl='5'
pb='5'
>
<NavList links={config.links} />
</Flex>
</Box>
);
}

View File

@ -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,46 +26,50 @@ const NavMobile: React.FC = () => {
<DrawerOverlay />
<DrawerContent backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}>
<DrawerBody>
<Flex as='nav' flexDirection='column' alignItems='flex-start' textDecoration='none'>
<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'
backgroundColor={colorMode === 'dark' ? 'gray.700' : 'gray.100'}
>
<Flex alignItems='center'>
<Avatar size='sm' name={`${userStore.me.first_name} ${userStore.me.last_name}`} marginRight={3} />
<Flex flexDirection='column'>
<Text as='span' mr={2} lineHeight='1.2'>
{`${userStore.me.first_name} ${userStore.me.last_name}`}
</Text>
<Link to={`/profile`} style={{ fontSize: '13px', textDecoration: 'underline' }} onClick={onClose}>
View profile
</Link>
<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
minHeight='60px'
padding='10px'
borderRadius='md'
marginTop='auto'
backgroundColor={colorMode === 'dark' ? 'gray.700' : 'gray.100'}
>
<Flex alignItems='center'>
<Avatar size='sm' name={`${userStore.me.first_name} ${userStore.me.last_name}`} marginRight={3} />
<Flex flexDirection='column'>
<Text as='span' mr={2} lineHeight='1.2'>
{`${userStore.me.first_name} ${userStore.me.last_name}`}
</Text>
<Link to={`/profile`} style={{ fontSize: '13px', textDecoration: 'underline' }} onClick={onClose}>
View profile
</Link>
</Flex>
<IconButton
onClick={logout}
aria-label='Logout'
backgroundColor={colorMode === 'dark' ? 'gray.600' : 'gray.200'}
marginLeft='auto'
icon={<MdExitToApp />}
/>
</Flex>
<IconButton aria-label='Logout' backgroundColor='gray.200' marginLeft='auto' icon={<MdExitToApp />} />
</Flex>
</Box>
) : (
<Skeleton
h='60px'
position='absolute'
borderRadius='md'
bottom={0}
right={0}
left={0}
padding='10px'
margin='5'
/>
)}
</Box>
) : (
<Skeleton
h='60px'
position='absolute'
borderRadius='md'
bottom={0}
right={0}
left={0}
padding='10px'
margin='5'
/>
)}
</Flex>
</DrawerBody>
</DrawerContent>
</Drawer>

View File

@ -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 => {
return <NavLink link={link} key={link.location} onClickLink={onClickLink} />;
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;
})}
</>
);

View File

@ -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);

View File

@ -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() {