refactor: page

This commit is contained in:
Sergey Yarkov 2022-02-02 19:03:44 +03:00
parent 7f3eabe3e4
commit a14ed5eff9
22 changed files with 167 additions and 131 deletions

View File

@ -53,7 +53,7 @@ const AddStudentsModal: React.FC<AddStudentsModalPropsType> = ({
icon={MdGroup}
heading='Add new students'
description='Attach new students to current course.'
confirmText='Add students'
confirmText='Add'
isOpen={isOpen}
onClose={onClose}
onProceed={handleAttachStudents}

View File

@ -1,17 +1,23 @@
import React from 'react';
import { BoxProps, Box, Flex, Heading, Text, Divider } from '@chakra-ui/react';
interface IPageHeadingProps extends BoxProps {
interface IPageComposition {
Heading: React.FC<IPageHeadingProps & BoxProps>;
Content: React.FC<BoxProps>;
Footer: React.FC<BoxProps>;
}
const Page: React.FC<BoxProps> & IPageComposition = props => {
return <Box {...props}>{props.children}</Box>;
};
interface IPageHeadingProps {
headingPrefix?: JSX.Element;
heading: string;
description?: string;
}
export const PageWrapper: React.FC<BoxProps> = props => {
return <Box {...props}>{props.children}</Box>;
};
export const PageHeading: React.FC<IPageHeadingProps> = props => {
Page.Heading = props => {
const { headingPrefix, heading, description, ...boxProps } = props;
return (
<Box {...boxProps}>
@ -26,15 +32,17 @@ export const PageHeading: React.FC<IPageHeadingProps> = props => {
);
};
export const PageContent: React.FC<BoxProps> = props => (
Page.Content = props => (
<Box mt='4' {...props}>
{props.children}
</Box>
);
export const PageFooter: React.FC<BoxProps> = props => (
Page.Footer = props => (
<Box mt='4' {...props}>
<Divider m='20px 0' />
{props.children}
</Box>
);
export { Page };

View File

@ -10,7 +10,7 @@ const Avatar: React.FC<IAvatarProps> = props => {
return (
<Flex flexDir={'column'} alignItems={'center'}>
<ChakraAvatar border={`4px solid ${borderColor}`} borderColor={borderColor} name={name} size='2xl'>
<ChakraAvatarBadge boxSize='0.75em' bg={onlineColor} />
{isOnline && <ChakraAvatarBadge boxSize='0.75em' bg={onlineColor} />}
</ChakraAvatar>
{props.children}
</Flex>

View File

@ -3,7 +3,7 @@ import { ChatButton } from './ChatButton';
export interface IAvatarProps {
name: string;
isOnline: boolean;
isOnline?: boolean | undefined;
}
export interface IChatButtonProps {

View File

@ -1,6 +1,5 @@
import React, { createContext } from 'react';
import { BoxProps, Flex } from '@chakra-ui/react';
import { IUser } from '@educt/interfaces';
import React from 'react';
import { BoxProps, Flex, FlexProps } from '@chakra-ui/react';
import { Avatar, ChatButton, IAvatarProps, IChatButtonProps } from './Avatar';
import { About, Details, Heading, IAboutProps, IDetailsProps, IHeadingProps, Info } from './Info';
@ -13,10 +12,12 @@ interface IUserProfileComposition {
About: React.FC<IAboutProps>;
}
export const UserProfileContext = createContext<IUser | undefined>(undefined);
const UserProfile: React.FC & IUserProfileComposition = props => {
return <Flex flexDir={{ base: 'column', md: 'row' }}>{props.children}</Flex>;
const UserProfile: React.FC<FlexProps> & IUserProfileComposition = props => {
return (
<Flex flexDir={{ base: 'column', md: 'row' }} {...props}>
{props.children}
</Flex>
);
};
UserProfile.Avatar = Avatar;

View File

@ -8,7 +8,7 @@ import { MdError } from 'react-icons/md';
* Types
*/
import { IPageProps } from '@educt/interfaces';
import { PageContent, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
/**
* Hooks
@ -28,8 +28,8 @@ const NotFoundPage: React.FC<IPageProps> = ({ title }) => {
<Helmet>
<title>{title}</title>
</Helmet>
<PageWrapper textAlign='center' mt={40}>
<PageContent>
<Page textAlign='center' mt={40}>
<Page.Content>
<Icon as={MdError} w='16' h='16' />
<Heading display='block' as='h1' fontSize='6xl'>
Page not found
@ -38,8 +38,8 @@ const NotFoundPage: React.FC<IPageProps> = ({ title }) => {
<Link as={ReactRouterLink} to='/' color='blue.500'>
Return to main page
</Link>
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
</>
);
};

View File

@ -1,10 +1,14 @@
import React from 'react';
import { IPageProps } from '@educt/interfaces';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import { CategoryTableList, CategoryTableRow } from '@educt/components/Tables/CategoryTableList';
import { useFetchCategories } from '@educt/hooks/queries';
import LoadingPage from '@educt/components/LoadingPage';
/**
* Hooks
*/
import { useFetchCategories } from '@educt/hooks/queries';
/**
* Categories page
*/
@ -14,12 +18,12 @@ const CategoriesPage: React.FC<IPageProps> = () => {
if (isLoading || categories === null) return <LoadingPage />;
return (
<PageWrapper>
<PageHeading heading='Categories' description='Edit categories in the system.' />
<PageContent>
<Page>
<Page.Heading heading='Categories' description='Edit categories in the system.' />
<Page.Content>
<CategoryTableList categories={categories} render={CategoryTableRow} />
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -15,7 +15,7 @@ import type { IPageProps } from '@educt/interfaces';
* Components
*/
import { CourseBackgroundImage, CourseHeader, CourseHeading, CourseInfo, LessonItem, LessonList } from '.';
import { PageWrapper, PageContent } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import LoadingPage from '@educt/components/LoadingPage';
/**
@ -50,7 +50,7 @@ const CoursePage: React.FC<IPageProps> = () => {
const handleEditCourse = () => history.push(`/courses/edit/${course.id}`);
return (
<PageWrapper maxW='1200px'>
<Page maxW='1200px'>
<Helmet>
<title>{course.title}</title>
</Helmet>
@ -64,7 +64,7 @@ const CoursePage: React.FC<IPageProps> = () => {
/>
</CourseHeader>
<PageContent mt={{ base: '5', sm: '2' }}>
<Page.Content mt={{ base: '5', sm: '2' }}>
<Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }} gap={'7'}>
<GridItem>
<Box>
@ -158,8 +158,8 @@ const CoursePage: React.FC<IPageProps> = () => {
</Box>
</GridItem>
</Grid>
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -8,7 +8,7 @@ import { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import { PageWrapper, PageHeading, PageContent } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import { CreateButton } from '@educt/components/Buttons';
import { Box } from '@chakra-ui/layout';
import LoadingPage from '@educt/components/LoadingPage';
@ -38,18 +38,18 @@ const CoursesPage: React.FC<IPageProps> = () => {
if (me === null) return <LoadingPage />;
return (
<PageWrapper>
<PageHeading heading='Courses' description='List of all courses'>
<Page>
<Page.Heading heading='Courses' description='List of all courses'>
<Box mt='5'>
{(me.isAdmin || me.isTeacher) && <CreateButton onClick={handleCreateCourse} ml='auto' display='block' />}
</Box>
</PageHeading>
<PageContent mt='10'>
</Page.Heading>
<Page.Content mt='10'>
<CoursesTabs mb='7' />
<CategoryList render={CategoryItem} />
<CourseList render={CourseItem} />
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import { IPageProps } from '@educt/interfaces';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import CreateLessonForm from '@educt/components/Forms/LessonForm/CreateLessonForm';
import { Redirect, useParams } from 'react-router-dom';
import { useFetchCourse } from '@educt/hooks/queries';
@ -21,12 +21,12 @@ const CreateLessonPage: React.FC<IPageProps> = () => {
if (!course || isLoading) return <LoadingPage />;
return (
<PageWrapper>
<PageHeading heading='Lesson Creation' description='Create new lesson' />
<PageContent>
<Page>
<Page.Heading heading='Lesson Creation' description='Create new lesson' />
<Page.Content>
<CreateLessonForm />
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -1,5 +1,4 @@
import React from 'react';
import { Flex, Box, Heading, Text } from '@chakra-ui/react';
/**
* Types
@ -11,23 +10,23 @@ import { IPageProps } from '@educt/interfaces';
*/
import PrevPageButton from '@educt/components/PrevPageButton';
import { CreateCourseForm } from '@educt/components/Forms/CourseForm';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
/**
* Create course page
*/
const CreateCoursePage: React.FC<IPageProps> = () => {
return (
<PageWrapper>
<PageHeading
<Page>
<Page.Heading
headingPrefix={<PrevPageButton prevPage='/courses' />}
heading='Course Creation'
description='Fill in the required fields to create a new course'
/>
<PageContent>
<Page.Content>
<CreateCourseForm />
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -17,7 +17,7 @@ import DeleteCourseDialog from '@educt/components/Dialogs/DeleteCourseDialog';
import PrevPageButton from '@educt/components/PrevPageButton';
import { DeleteButton } from '@educt/components/Buttons';
import { StatusButton } from './components';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import { StudentTableList, StudentTableRow } from './components/StudentTableList';
import LessonList from './components/LessonList';
@ -43,13 +43,13 @@ const EditCoursePage: React.FC<IPageProps> = () => {
if (!course || isLoading) return <LoadingPage />;
return (
<PageWrapper>
<Page>
<Helmet>
<title>Editing {course.title}</title>
</Helmet>
<DeleteCourseDialog course={course} onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />
<PageHeading
<Page.Heading
headingPrefix={<PrevPageButton prevPage='/courses' />}
heading='Course editor'
description={course.title}
@ -58,8 +58,8 @@ const EditCoursePage: React.FC<IPageProps> = () => {
<StatusButton courseId={course.id} currentStatus={course.status} />
<DeleteButton onClick={onOpenDeleteDialog} ml='2' />
</Flex>
</PageHeading>
<PageContent>
</Page.Heading>
<Page.Content>
<Box>
<Tabs mt='8'>
<TabList>
@ -91,8 +91,8 @@ const EditCoursePage: React.FC<IPageProps> = () => {
</TabPanels>
</Tabs>
</Box>
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -22,7 +22,7 @@ import {
import ReactPlayer from 'react-player';
import { MdOutlineCircle, MdCheckCircle, MdOutlineFilePresent, MdSkipNext, MdSkipPrevious } from 'react-icons/md';
import LoadingPage from '@educt/components/LoadingPage';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
/**
* Types
@ -138,17 +138,17 @@ const LessonPage: React.FC<IPageProps> = () => {
if (me === null) return <LoadingPage />;
return (
<PageWrapper>
<Page>
<Helmet>
<title>{lesson.title}</title>
</Helmet>
<PageHeading heading={lesson.title}>
<Page.Heading heading={lesson.title}>
<Flex mt='3' alignItems={'center'} justifyContent={'space-between'}>
<Text fontSize={'sm'}>~{moment.duration(lesson.duration, 'minutes').humanize()}</Text>
{(me.isAdmin || me.isTeacher) && <EditButton onClick={handleEditLesson} />}
</Flex>
</PageHeading>
<PageContent>
</Page.Heading>
<Page.Content>
<Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }}>
<GridItem>
<Box>
@ -270,8 +270,8 @@ const LessonPage: React.FC<IPageProps> = () => {
</Box>
</GridItem>
</Grid>
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -1,10 +1,10 @@
import React from 'react';
import { IPageProps } from '@educt/interfaces';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import { Redirect, useParams } from 'react-router-dom';
import LoadingPage from '@educt/components/LoadingPage';
import { useFetchLesson } from '@educt/hooks/queries/lesson/useFetchLesson';
import EditLessonForm from '@educt/components/Forms/LessonForm/EditLessonForm';
import { useFetchLesson } from '@educt/hooks/queries/lesson/useFetchLesson';
/**
* Create Lesson page
@ -20,12 +20,12 @@ const EditLessonPage: React.FC<IPageProps> = () => {
if (!lesson || isLoading) return <LoadingPage />;
return (
<PageWrapper>
<PageHeading heading='Lesson Editor' description={lesson.description} />
<PageContent>
<Page>
<Page.Heading heading='Lesson Editor' description={lesson.description} />
<Page.Content>
<EditLessonForm lesson={lesson} />
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -34,7 +34,7 @@ export const CourseCard: React.FC<BoxProps> = props => <Card py='5' px='6' {...p
export const CourseCardHeading: React.FC<BoxProps> = props => (
<Box borderLeftWidth={'5px'} borderColor={'blue.500'} pl='2' {...props} />
);
export const CourseCardTitle: React.FC<TextProps> = props => <Text fontSize={'2xl'} fontWeight={'bold'} {...props} />;
export const CourseCardTitle: React.FC<TextProps> = props => <Text fontSize={'2xl'} fontWeight={'medium'} {...props} />;
export const CourseCardCategory: React.FC = props => (
<Flex mt='2'>
<Tag size='sm' borderRadius='full' variant={'solid'}>

View File

@ -1,8 +1,8 @@
import React, { useState } from 'react';
import React from 'react';
import { Flex, Box, Text, Link, SimpleGrid, Divider } from '@chakra-ui/react';
import { Link as ReactRouterLink } from 'react-router-dom';
import { IPageProps } from '@educt/interfaces';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import { Stat, StatContent, StatIcon, StatLabel, StatNumber } from '@educt/components/Stat';
import { MdOutlineCollectionsBookmark, MdOutlineGroup, MdOutlineMessage, MdOutlineVideoLibrary } from 'react-icons/md';
import { useRootStore } from '@educt/hooks/useRootStore';
@ -21,9 +21,13 @@ import {
CourseCardList,
} from './components';
import { observer } from 'mobx-react';
/**
* Hooks
*/
import { useEffect, useState } from 'react';
import { useFetchCourses, useFetchStat } from '@educt/hooks/queries';
import { useSocketEvent } from '@educt/hooks/useSocketEvent';
import { useEffect } from 'react';
/**
* Main page
@ -52,9 +56,9 @@ const MainPage: React.FC<IPageProps> = () => {
if (me === null || courses === null || stat === null) return <LoadingPage />;
return (
<PageWrapper>
<PageHeading heading={`👋 Hello ${me.first_name},`} description="This what we've got today for you." />
<PageContent>
<Page>
<Page.Heading heading={`👋 Hello ${me.first_name},`} description="This what we've got today for you." />
<Page.Content>
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing='6'>
<Stat>
<StatLabel>Online</StatLabel>
@ -121,8 +125,8 @@ const MainPage: React.FC<IPageProps> = () => {
</CourseCardList>
</Box>
<Divider />
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};

View File

@ -1,5 +1,6 @@
import React from 'react';
import { Box, Heading, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Grid } from '@chakra-ui/react';
import { Box, Heading } from '@chakra-ui/react';
import { Page } from '@educt/components/PageElements';
import { IPageProps } from '@educt/interfaces';
@ -8,9 +9,9 @@ import { IPageProps } from '@educt/interfaces';
*/
const MessagesPage: React.FC<IPageProps> = () => {
return (
<Box>
<Heading as='h1'>Messages page</Heading>
</Box>
<Page>
<Page.Heading heading='Messages' />
</Page>
);
};

View File

@ -69,16 +69,6 @@ export const ProfileDescription: React.FC<IProfileDescriptionProps> = props => {
);
};
export const ProfileSignOutButton: React.FC<ButtonProps> = () => {
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}>

View File

@ -1,6 +1,5 @@
import React from 'react';
import {
Avatar,
Tabs,
TabList,
Tab,
@ -23,8 +22,9 @@ import { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import { PageHeading, PageContent, PageWrapper, PageFooter } from '@educt/components/PageElements';
import { ProfileAvatar, ProfileBaseInfo, ProfileDescription, ProfileLoading, ProfileSignOutButton } from './components';
import { Page } from '@educt/components/PageElements';
import { ProfileLoading } from './components';
import { UserProfile } from '@educt/components/UserProfile';
import { CourseList } from './components/CourseList';
import UpdateUserContactsForm from '@educt/pages/profile/components/UpdateUserContactsForm';
@ -33,6 +33,7 @@ import UpdateUserContactsForm from '@educt/pages/profile/components/UpdateUserCo
*/
import { useHistory } from 'react-router-dom';
import { useRootStore } from '@educt/hooks/useRootStore';
import { observer } from 'mobx-react';
/**
* Profile page
@ -46,13 +47,17 @@ const ProfilePage: React.FC<IPageProps> = () => {
if (me === null) return <ProfileLoading />;
return (
<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>
<Page maxW={'900px'}>
<Page.Heading heading='My account' description='Update you contacts information or password here.' />
<Page.Content>
<UserProfile mt='14'>
<UserProfile.Avatar name={me.fullname} />
<UserProfile.Info>
<UserProfile.Heading fullname={me.fullname} roles={me.roles} />
<UserProfile.Details registered={me.created_at} lastLogin={me.last_login} />
<UserProfile.About about={me.about} />
</UserProfile.Info>
</UserProfile>
<Tabs mt='7'>
<TabList>
<Tab>Account information</Tab>
@ -116,12 +121,9 @@ const ProfilePage: React.FC<IPageProps> = () => {
)}
</TabPanels>
</Tabs>
</PageContent>
<PageFooter textAlign='center'>
<ProfileSignOutButton />
</PageFooter>
</PageWrapper>
</Page.Content>
</Page>
);
};
export default ProfilePage;
export default observer(ProfilePage);

View File

@ -1,7 +1,8 @@
import React from 'react';
import { observer } from 'mobx-react';
import { Redirect } from 'react-router-dom';
import LoadingPage from '@educt/components/LoadingPage';
import { PageContent, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import { UserProfile } from '@educt/components/UserProfile';
import type { IPageProps } from '@educt/interfaces';
@ -10,18 +11,27 @@ import type { IPageProps } from '@educt/interfaces';
*/
import { useParams } from 'react-router-dom';
import { useFetchUser } from '@educt/hooks/queries/user/useFetchUser';
import { useRootStore } from '@educt/hooks/useRootStore';
const UserPage: React.FC<IPageProps> = () => {
const { id } = useParams<{ id: string }>();
const { data: user, error, isLoading } = useFetchUser(id);
const params = useParams<{ id: string }>();
const {
userStore: { me },
} = useRootStore();
const { data: user, error, isLoading } = useFetchUser(params.id);
if (error?.response?.status === 404) return <Redirect to='/404' />;
if (user === null || isLoading) return <LoadingPage />;
if (user === null || me === null || isLoading) return <LoadingPage />;
/**
* Redirect to profile page
*/
if (me.id === params.id) return <Redirect to='/profile' />;
return (
<PageWrapper maxW='800px'>
<PageContent>
<Page>
<Page.Content maxW={'900px'}>
<UserProfile>
<UserProfile.Avatar name={user.fullname} isOnline>
<UserProfile.ChatButton userid={user.id} />
@ -32,9 +42,9 @@ const UserPage: React.FC<IPageProps> = () => {
<UserProfile.About about={user.about} />
</UserProfile.Info>
</UserProfile>
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
);
};
export default UserPage;
export default observer(UserPage);

View File

@ -8,7 +8,7 @@ import type { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { Page } from '@educt/components/PageElements';
import { UserList, UserItem } from './components/UserList';
import LoadingPage from '@educt/components/LoadingPage';
@ -35,12 +35,12 @@ const UsersPage: React.FC<IPageProps> = () => {
return (
// TODO: delete page context for better performance
<UsersPageContextProvider>
<PageWrapper>
<PageHeading heading='User management' description='You can add or delete users on this page.' />
<PageContent mt='5'>
<Page>
<Page.Heading heading='User management' description='You can add or delete users on this page.' />
<Page.Content mt='5'>
<UserList render={UserItem} limit={6} />
</PageContent>
</PageWrapper>
</Page.Content>
</Page>
</UsersPageContextProvider>
);
};

View File

@ -62,10 +62,25 @@ export default class UserStore {
public async loadCurrentUserData() {
const result = await UserServiceInstance.fetchMe();
const {
data: { id, first_name, last_name, fullname, email, roles, likes, contacts, courses, created_at, updated_at },
data: {
id,
first_name,
last_name,
fullname,
about,
last_login,
email,
roles,
likes,
contacts,
courses,
created_at,
updated_at,
},
} = result;
runInAction(() => {
// TODO Fix this
this.me = {
id,
first_name,
@ -73,6 +88,8 @@ export default class UserStore {
email,
roles,
fullname,
about,
last_login,
likes,
contacts,
courses,