refactor: page elements layout

This commit is contained in:
Sergey Yarkov 2021-12-07 23:00:23 +03:00
parent 546a92bc8c
commit 9fb8b18b23
23 changed files with 264 additions and 194 deletions

View File

@ -1,9 +1,22 @@
import React from 'react';
import { ButtonProps, Button } from '@chakra-ui/button';
import { AddIcon } from '@chakra-ui/icons';
import { AddIcon, EditIcon } from '@chakra-ui/icons';
import { MdSave } from 'react-icons/md';
export const CreateButton: React.FC<ButtonProps> = props => (
<Button variant='outline' size='sm' leftIcon={<AddIcon />} {...props}>
<Button variant='outline' size='sm' loadingText='Creating...' leftIcon={<AddIcon />} {...props}>
Create new
</Button>
);
export const EditButton: React.FC<ButtonProps> = props => (
<Button leftIcon={<EditIcon />} variant='outline' size='sm' {...props}>
Edit
</Button>
);
export const SaveButton: React.FC<ButtonProps> = props => (
<Button size='sm' variant='outline' loadingText='Saving...' rightIcon={<MdSave />} {...props}>
Save
</Button>
);

View File

@ -1,6 +1,7 @@
import React from 'react';
import Cookies from 'js-cookie';
import { Flex, Box, Heading, Text } from '@chakra-ui/react';
import { Redirect } from 'react-router';
import { Icon, Box, Heading, Text } from '@chakra-ui/react';
import { MdError } from 'react-icons/md';
/**
@ -9,7 +10,6 @@ import { MdError } from 'react-icons/md';
import { useEffect } from 'react';
import { useToast } from '@chakra-ui/toast';
import { useRootStore } from '@educt/hooks/useRootStore';
import { Redirect } from 'react-router';
const ErrorFallback: React.FC<{ error: any }> = props => {
const { userStore, authStore } = useRootStore();
@ -30,9 +30,7 @@ const ErrorFallback: React.FC<{ error: any }> = props => {
return (
<Box textAlign='center' mt={40}>
<Flex justifyContent='center'>
<MdError size='64px' />
</Flex>
<Icon as={MdError} w='16' h='16' />
<Heading display='block' as='h1' fontSize='6xl'>
Something went wrong
</Heading>

View File

@ -0,0 +1,32 @@
import React from 'react';
import { BoxProps, Box, Heading, Text, Divider } from '@chakra-ui/react';
interface IPageHeadingProps extends BoxProps {
heading: string;
description?: string;
}
export const PageWrapper: React.FC<BoxProps> = props => {
return <Box {...props}>{props.children}</Box>;
};
export const PageHeading: React.FC<IPageHeadingProps> = props => {
const { heading, description, ...boxProps } = props;
return (
<Box {...boxProps}>
<Heading as='h1'>{heading}</Heading>
{description && <Text mt='2'>{description}</Text>}
{props.children}
<Divider mt='5' />
</Box>
);
};
export const PageContent: React.FC<BoxProps> = props => <Box {...props}>{props.children}</Box>;
export const PageFooter: React.FC<BoxProps> = props => (
<Box mt='4' {...props}>
<Divider m='20px 0' />
{props.children}
</Box>
);

View File

@ -1,38 +1,30 @@
import { Badge } from '@chakra-ui/react';
import { BadgeProps, Badge, Box } from '@chakra-ui/react';
import React from 'react';
import { UserRoleEnum } from '@educt/enums';
import { IUserRole } from '@educt/interfaces';
import { userHasRoles } from '@educt/helpers';
/**
* Returns the badge for user based on roles
*/
const UserBadge: React.FC<{ roles: IUserRole[] }> = ({ roles }) => {
export interface IUserBadgeProps extends BadgeProps {
roles: IUserRole[];
}
const UserBadge: React.FC<IUserBadgeProps> = props => {
const { roles } = props;
const getCurrentRole = () => {
if (userHasRoles(roles, [UserRoleEnum.ADMIN])) return { name: 'Administrator', color: 'purple' };
if (userHasRoles(roles, [UserRoleEnum.TEACHER])) return { name: 'Teacher', color: 'orange' };
if (userHasRoles(roles, [UserRoleEnum.STUDENT])) return { name: 'Student', color: 'blue' };
/**
* The user has no roles
*/
return { name: 'Guest', color: 'gray' };
};
const role = getCurrentRole();
if (role !== undefined) {
return (
<Badge colorScheme={role.color} variant='subtle'>
{role.name}
</Badge>
);
}
/**
* Cannot find any role for user
*/
return null;
return (
<Badge colorScheme={role.color} variant='subtle' {...props}>
{role.name}
</Badge>
);
};
export default UserBadge;

View File

@ -43,6 +43,7 @@ export interface ILesson {
description: string;
duration: string;
color?: ColorType | undefined | null;
materials_count?: string | undefined;
created_at: string;
updated_at: string;
}

View File

@ -1,18 +1,25 @@
import React from 'react';
import Helmet from 'react-helmet';
import { Link as ReactRouterLink } from 'react-router-dom';
import { Flex, Box, Heading, Text, useToast, Link } from '@chakra-ui/react';
import { Flex, Heading, Text, Link, Icon } from '@chakra-ui/react';
import { MdError } from 'react-icons/md';
/**
* Types
*/
import { IPageProps } from '@educt/interfaces';
import { PageContent, PageWrapper } from '@educt/components/PageElements';
/**
* Hooks
*/
import { useEffect } from 'react';
import { useToast } from '@chakra-ui/react';
const NotFoundPage: React.FC<IPageProps> = ({ title }) => {
const toast = useToast();
React.useEffect(() => {
useEffect(() => {
toast({ title: `Page not found`, duration: 10000, isClosable: true, status: 'warning' });
});
@ -21,18 +28,18 @@ const NotFoundPage: React.FC<IPageProps> = ({ title }) => {
<Helmet>
<title>{title}</title>
</Helmet>
<Box textAlign='center' mt={40}>
<Flex justifyContent='center'>
<MdError size='64px' />
</Flex>
<Heading display='block' as='h1' fontSize='6xl'>
Page not found
</Heading>
<Text mt='5'>Sorry the page you are looking for could not be found.</Text>
<Link as={ReactRouterLink} to='/' color='blue.500'>
Return to main page
</Link>
</Box>
<PageWrapper textAlign='center' mt={40}>
<PageContent>
<Icon as={MdError} w='16' h='16' />
<Heading display='block' as='h1' fontSize='6xl'>
Page not found
</Heading>
<Text mt='5'>Sorry the page you are looking for could not be found.</Text>
<Link as={ReactRouterLink} to='/' color='blue.500'>
Return to main page
</Link>
</PageContent>
</PageWrapper>
</>
);
};

View File

@ -1,8 +1,8 @@
import React from 'react';
import Helmet from 'react-helmet';
import { observer } from 'mobx-react';
import { Redirect } from 'react-router-dom';
import { Box, Heading, Text, Link } from '@chakra-ui/react';
import { observer } from 'mobx-react';
import { Box, Heading } from '@chakra-ui/react';
/**
* Types

View File

@ -23,7 +23,7 @@ const CourseStatusTabs: React.FC<CourseStatusTabsProps> = () => {
const handleChangeStatus = (status: CourseStatusEnum | undefined) => setCourseStatus(status);
return (
<Tabs>
<Tabs variant='solid-rounded'>
<TabList justifyContent='center'>
<Tab onClick={() => handleChangeStatus(undefined)}>All</Tab>
<Tab onClick={() => handleChangeStatus(CourseStatusEnum.PUBLISHED)}>Published</Tab>

View File

@ -1,6 +1,5 @@
import React from 'react';
import { Flex, Box, Heading, Text, Button } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { Box } from '@chakra-ui/react';
/**
* Types
@ -10,10 +9,13 @@ import { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import { PageWrapper, PageHeading, PageContent } from '@educt/components/PageElements';
import LoadingPage from '@educt/components/LoadingPage';
import CourseList from './components/CourseList';
import CategoryList from './components/CategoryList';
import CourseStatusTabs from './components/CourseStatusTabs';
import LoadingPage from '@educt/components/LoadingPage';
import CategoryItem from './components/CategoryList/CategoryItem';
import CourseItem from './components/CourseList/CourseItem';
/**
* Providers
@ -24,10 +26,6 @@ import { CoursesPageContextProvider } from '@educt/providers';
* Hooks
*/
import { useRootStore } from '@educt/hooks/useRootStore';
import { useHistory } from 'react-router';
import CategoryItem from './components/CategoryList/CategoryItem';
import CourseItem from './components/CourseList/CourseItem';
import { CreateButton } from '@educt/components/Buttons';
/**
* Courses page
@ -36,28 +34,21 @@ const CoursesPage: React.FC<IPageProps> = () => {
const {
userStore: { me },
} = useRootStore();
const history = useHistory();
if (me === null) return <LoadingPage />;
return (
<CoursesPageContextProvider>
<Box>
<Flex alignItems='center' justifyContent='space-between'>
<Box>
<Heading as='h1'>Courses</Heading>
<Text mt='2'>List of all courses.</Text>
</Box>
{(me.isAdmin || me.isTeacher) && <CreateButton onClick={() => history.push('/courses/create')} />}
</Flex>
<Box mt='7'>
<PageWrapper>
<PageHeading heading='Courses' description='List of all courses' />
<PageContent mt='10'>
{(me.isAdmin || me.isTeacher) && <CourseStatusTabs />}
<Box mt='5'>
<CategoryList render={CategoryItem} />
<CourseList render={CourseItem} />
</Box>
</Box>
</Box>
</PageContent>
</PageWrapper>
</CoursesPageContextProvider>
);
};

View File

@ -35,6 +35,7 @@ import { useErrorHandler } from 'react-error-boundary';
* Services
*/
import { LessonServiceInstance } from '@educt/services';
import { CreateButton } from '@educt/components/Buttons';
type LessonListPropsType = {
course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
@ -169,7 +170,7 @@ const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
<Icon as={MdAttachment} />
</Text>
<Text as='small' verticalAlign='middle'>
4 attachments
{lesson.materials_count} attachments
</Text>
</Text>
</Flex>
@ -205,10 +206,10 @@ const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
<DeleteLessonDialog lesson={deleting} isOpen={isOpenDeleteDialog} onClose={onCloseDeleteDialog} />
)}
<Flex mt='2' mb='3' padding='0 20px' alignItems='center' justifyContent='space-between'>
<Text fontWeight='medium'>Total: ({lessons.length})</Text>
<Button onClick={handleCreateLesson} variant='outline' colorScheme='blue'>
Add new
</Button>
<Text fontSize='sm' color='gray.500'>
{lessons.length} lessons
</Text>
<CreateButton onClick={handleCreateLesson} />
</Flex>
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable

View File

@ -18,6 +18,7 @@ import type { ICourse } from '@educt/interfaces';
import { useCallback, useState } from 'react';
import { useDisclosure } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { CreateButton } from '@educt/components/Buttons';
type StudentTableListPropsType = {
render: React.FC<StudentTableRowPropsType>;
@ -89,7 +90,7 @@ const StudentTableList: React.FC<StudentTableListPropsType> = ({ render: Row, st
<AddStudentsModal isOpen={isOpenAddStudentModal} onClose={onCloseAddStudentModal} />
<Flex justifyContent='space-between' flexDir={{ base: 'column', lg: 'row' }}>
<Flex mb='2'>
<InputGroup mr='2'>
<InputGroup mr='2' borderRadius='lg'>
<InputLeftElement pointerEvents='none' children={<MdSearch color='gray.300' />} />
<Input value={search} size='sm' onChange={handleSearch} type='text' placeholder='Search for student...' />
</InputGroup>
@ -104,9 +105,7 @@ const StudentTableList: React.FC<StudentTableListPropsType> = ({ render: Row, st
<BulkActionsMenu selected={selected} rows={rows} setRows={setRows} />
</>
)}
<Button onClick={onOpenAddStudentModal} leftIcon={<AddIcon />} size='sm' variant='outline'>
Add new
</Button>
<CreateButton onClick={onOpenAddStudentModal} />
</Flex>
</Flex>
<Table overflow='hidden' borderRadius='lg' mt='2'>

View File

@ -1,7 +1,7 @@
import React from 'react';
import moment from 'moment';
import { Link as ReactRouterLink } from 'react-router-dom';
import { Tag, TagLabel, LinkBox, Text, LinkOverlay, Icon, Link } from '@chakra-ui/react';
import { Tag, TagLabel, LinkBox, Text, LinkOverlay, Icon } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/layout';
/**
@ -14,6 +14,7 @@ import { TimeIcon } from '@chakra-ui/icons';
* Hooks
*/
import { useColorModeValue } from '@chakra-ui/color-mode';
import { MdOutlineVideoLibrary } from 'react-icons/md';
type CourseItemPropsType = {
course: UserCourseType;
@ -22,30 +23,11 @@ type CourseItemPropsType = {
const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
return (
<LinkBox as='div' p='3' borderBottomWidth='1px' borderRadius='lg' bg={useColorModeValue('gray.50', 'gray.700')}>
<Flex justifyContent='space-between'>
<Box>
<Tag variant='outline' borderRadius='full' alignSelf='flex-start'>
<TagLabel>{course.lessons_count} lessons</TagLabel>
</Tag>
</Box>
<Box>
<Tag
color='white'
boxShadow={`inset 0 0 0px 1px ${course.category.color?.hex}`}
bg={course.category.color?.hex}
borderRadius='full'
alignSelf='flex-start'
>
<TagLabel>{course.category.title}</TagLabel>
</Tag>
</Box>
</Flex>
<Flex justifyContent='space-between' mt='2'>
<Text>
<LinkOverlay
as={ReactRouterLink}
fontSize='xl'
fontSize='md'
fontWeight='medium'
to={`/course/${course.id}`}
overflowWrap='anywhere'
@ -53,9 +35,22 @@ const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
{course.title}
</LinkOverlay>
</Text>
<Box>
<Tag
color='white'
boxShadow={`inset 0 0 0px 1px ${course.category.color?.hex}`}
bg={course.category.color?.hex}
alignSelf='flex-start'
variant='outline'
>
<TagLabel>{course.category.title}</TagLabel>
</Tag>
</Box>
</Flex>
<Flex mt='2' alignItems='center' sx={{ gap: '9px' }}>
<Flex mt='2' alignItems='center' fontSize='sm' sx={{ gap: '9px' }}>
<Icon as={MdOutlineVideoLibrary} w='14px' h='14px' />
<Text mr='1'>{course.lessons_count} lessons</Text>
<Icon as={TimeIcon} w='14px' h='14px' />
<Box
as='time'

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Box, Text, Flex, Stack } from '@chakra-ui/layout';
import { Box, Text, Stack } from '@chakra-ui/layout';
/**
* Types

View File

@ -1,3 +1,4 @@
import CourseList from './CourseList';
import CourseItem from './CourseItem';
export default CourseList;
export { CourseList, CourseItem };

View File

@ -1,24 +0,0 @@
import React from 'react';
import { Box, Skeleton, SkeletonCircle, Flex, Spinner } from '@chakra-ui/react';
const LoadingPage: React.FC = () => {
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 LoadingPage;

View File

@ -1,3 +0,0 @@
import LoadingPage from './LoadingPage';
export default LoadingPage;

View File

@ -23,6 +23,7 @@ import { useErrorHandler } from 'react-error-boundary';
* Schema
*/
import UpdateUserContactsSchema from './UpdateUserContactsForm.validator';
import { SaveButton } from '@educt/components/Buttons';
type UpdateUserContactsInputType = {
phone_number: string | null;
@ -152,19 +153,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ con
</FormControl>
</Stack>
</Box>
<Button
colorScheme='blue'
mt='4'
type='submit'
size='md'
variant='outline'
isLoading={isLoading}
loadingText='Saving...'
rightIcon={<MdSave />}
isDisabled={!isDirty}
>
Save
</Button>
<SaveButton type='submit' mt='4' isLoading={isLoading} rightIcon={<MdSave />} isDisabled={!isDirty} />
</form>
</Box>
);

View File

@ -0,0 +1,98 @@
import React from 'react';
import { FlexProps, Flex, BoxProps, Box, Heading, Text } from '@chakra-ui/layout';
import { Avatar } from '@chakra-ui/avatar';
import { Button, ButtonProps } from '@chakra-ui/button';
import { Skeleton, SkeletonCircle, Spinner } from '@chakra-ui/react';
/**
* Types
*/
import { IUserRole } from '@educt/interfaces';
/**
* Components
*/
import UserBadge from '@educt/components/UserBadge';
/**
* Hooks
*/
import { useColorMode } from '@chakra-ui/react';
import { useLogout } from '@educt/hooks/queries';
export const ProfileBaseInfo: React.FC<FlexProps> = props => {
return (
<Flex flexDir={{ base: 'column', md: 'row' }} textAlign={{ base: 'center', md: 'left' }} {...props}>
{props.children}
</Flex>
);
};
interface IProfileAvatarProps extends BoxProps {
name: string;
}
export const ProfileAvatar: React.FC<IProfileAvatarProps> = props => {
const { colorMode } = useColorMode();
const isDark = colorMode === 'dark';
return (
<Box>
<Avatar border={`4px solid ${isDark ? '#2D3748' : '#E2E8F0'}`} size='2xl' name={props.name} />
</Box>
);
};
interface IProfileDescriptionProps extends BoxProps {
fullname: string;
email: string;
roles: IUserRole[];
}
export const ProfileDescription: React.FC<IProfileDescriptionProps> = props => {
const { fullname, email, roles, ...boxProps } = props;
return (
<Box w='full' ml={{ md: '50' }} mt={{ base: '2', md: '3' }} {...boxProps}>
<Flex alignItems='center' justifyContent='space-between'>
<Box>
<Heading lineHeight='1.5rem' fontWeight='bold' fontSize='24px'>
{fullname}
<br />
<Text as='span' fontSize='sm' fontWeight='normal'>
{email}
</Text>
</Heading>
<UserBadge mt='2' roles={roles} />
</Box>
</Flex>
</Box>
);
};
export const ProfileSignOutButton: React.FC<ButtonProps> = props => {
const { logout } = useLogout();
return (
<Button onClick={logout} size='sm' w={{ base: 'full', sm: '150px' }}>
Sign Out
</Button>
);
};
export const ProfileLoading: React.FC<BoxProps> = props => {
return (
<Box maxW='900px' {...props}>
<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>
);
};

View File

@ -13,7 +13,7 @@ import {
InputGroup,
InputRightElement,
} from '@chakra-ui/react';
import { Box, Flex, Heading, Text, Stack } from '@chakra-ui/layout';
import { Box, Stack } from '@chakra-ui/layout';
/**
* Types
@ -23,18 +23,16 @@ import { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import LoadingPage from './components/LoadingPage';
import UserBadge from '@educt/components/UserBadge';
import { PageHeading, PageContent, PageWrapper, PageFooter } from '@educt/components/PageElements';
import { ProfileAvatar, ProfileBaseInfo, ProfileDescription, ProfileLoading, ProfileSignOutButton } from './components';
import { CourseList } from './components/CourseList';
import UpdateUserContactsForm from '@educt/pages/profile/components/UpdateUserContactsForm';
import CourseList from '@educt/pages/profile/components/CourseList';
/**
* Hooks
*/
import { useHistory } from 'react-router-dom';
import { useColorMode } from '@chakra-ui/color-mode';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useLogout } from '@educt/hooks/queries';
/**
* Profile page
@ -44,35 +42,17 @@ const ProfilePage: React.FC<IPageProps> = () => {
userStore: { me },
} = useRootStore();
const history = useHistory();
const { logout } = useLogout();
const { colorMode } = useColorMode();
if (me === null) return <LoadingPage />;
if (me === null) return <ProfileLoading />;
return (
<Box maxW='900px'>
<Heading as='h1'>My account</Heading>
<Text mt='2'>Update you contacts information or password here.</Text>
<Flex
alignItems='center'
flexDir={{ base: 'column', md: 'row' }}
textAlign={{ base: 'center', md: 'left' }}
mt='20'
>
<Box>
<Avatar border={`4px solid ${colorMode === 'dark' ? '#2D3748' : '#E2E8F0'}`} size='2xl' name={me.fullname} />
</Box>
<Flex ml={{ md: '50' }} mt={{ base: '2', md: '0' }} flexDirection='column'>
<Heading as='h2'>{me.fullname}</Heading>
<Flex alignItems='center' flexDir={{ base: 'column', md: 'row' }}>
<Text mr='10px' mb={{ base: '2', md: '0' }}>
{me.email}
</Text>
<UserBadge roles={me.roles} />
</Flex>
</Flex>
</Flex>
<Box mt='10'>
<PageWrapper maxW='900px'>
<PageHeading heading='My account' description='Update you contacts information or password here.' />
<ProfileBaseInfo mt='20'>
<ProfileAvatar name={me.fullname} />
<ProfileDescription fullname={me.fullname} email={me.email} roles={me.roles} />
</ProfileBaseInfo>
<PageContent>
<Tabs>
<TabList>
<Tab>Account information</Tab>
@ -82,7 +62,7 @@ const ProfilePage: React.FC<IPageProps> = () => {
<TabPanels mt='2'>
<TabPanel>
<form>
<Box borderRadius='lg' borderWidth='1px' padding='5'>
<Box borderRadius='md' borderWidth='1px' padding='5'>
<Stack spacing='5px'>
<FormControl isDisabled>
<FormHelperText color='gray.500'>First name</FormHelperText>
@ -136,13 +116,11 @@ const ProfilePage: React.FC<IPageProps> = () => {
)}
</TabPanels>
</Tabs>
</Box>
<Box textAlign='center' mt='3'>
<Button onClick={logout} size='sm' w={{ base: 'full', sm: '150px' }}>
Sign Out
</Button>
</Box>
</Box>
</PageContent>
<PageFooter textAlign='center'>
<ProfileSignOutButton />
</PageFooter>
</PageWrapper>
);
};

View File

@ -30,6 +30,8 @@ import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useDisclosure } from '@chakra-ui/hooks';
import { IUser } from '@educt/interfaces';
import UserSearch from '../UserSearch';
import CreateUserForm from '../CreateUserForm';
type UserListPropsType = { render: React.FC<UserItemPropsType>; limit?: number };
@ -76,6 +78,10 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item, limit }) => {
{deleting && <DeleteUserDialog user={deleting} isOpen={isOpenDeleteDialog} onClose={onCloseDeleteDialog} />}
{users.length !== 0 ? (
<>
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
<UserSearch />
<CreateUserForm />
</Flex>
<Box>
<Flex mt='5' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'>
<Text>Total: ({pagination.total})</Text>

View File

@ -1,3 +1,4 @@
import UserList from './UserList';
import UserItem from './UserItem';
export default UserList;
export { UserList, UserItem };

View File

@ -1,19 +1,16 @@
import React from 'react';
import { Box, Flex, Heading, Text } from '@chakra-ui/react';
/**
* Types
*/
import { IPageProps } from '@educt/interfaces';
import type { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { UserList, UserItem } from './components/UserList';
import LoadingPage from '@educt/components/LoadingPage';
import UserSearch from './components/UserSearch';
import UserList from './components/UserList';
import UserItem from './components/UserList/UserItem';
import CreateUserForm from './components/CreateUserForm';
/**
* Hooks
@ -36,18 +33,14 @@ const UsersPage: React.FC<IPageProps> = () => {
if (me === null) return <LoadingPage />;
return (
// TODO: delete page context for better performance
<UsersPageContextProvider>
<Box>
<Heading as='h1'>User management</Heading>
<Text mt='2'>You can add or delete users on this page.</Text>
<Flex mt='5' flexDir='column' h='full'>
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
<UserSearch />
<CreateUserForm />
</Flex>
<PageWrapper>
<PageHeading heading='User management' description='You can add or delete users on this page.' />
<PageContent mt='5'>
<UserList render={UserItem} limit={6} />
</Flex>
</Box>
</PageContent>
</PageWrapper>
</UsersPageContextProvider>
);
};

View File

@ -60,7 +60,7 @@ export default class UserStore {
try {
const result = await UserServiceInstance.fetchMe();
const {
data: { id, first_name, last_name, fullname, email, roles, contacts, courses },
data: { id, first_name, last_name, fullname, email, roles, contacts, courses, created_at, updated_at },
} = result;
runInAction(() => {
@ -76,6 +76,8 @@ export default class UserStore {
isAdmin: helpers.userContainRoles(roles, [UserRoleEnum.ADMIN]),
isTeacher: helpers.userContainRoles(roles, [UserRoleEnum.TEACHER]),
isStudent: helpers.userContainRoles(roles, [UserRoleEnum.STUDENT]),
created_at,
updated_at,
};
});