From 9fb8b18b238ec6e8458da212ab1a0b90db6de0fa Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Tue, 7 Dec 2021 23:00:23 +0300 Subject: [PATCH] refactor: page elements layout --- src/components/Buttons/index.tsx | 17 +++- src/components/ErrorFallback.tsx | 8 +- src/components/PageElements/index.tsx | 32 ++++++ src/components/UserBadge/UserBadge.tsx | 32 +++--- src/interfaces/index.ts | 1 + src/pages/404/404.tsx | 35 ++++--- src/pages/auth/auth.tsx | 4 +- .../CourseStatusTabs/CourseStatusTabs.tsx | 2 +- src/pages/courses/courses.tsx | 29 ++---- .../edit/components/LessonList/LessonList.tsx | 11 ++- .../StudentTableList/StudentTableList.tsx | 7 +- .../components/CourseList/CourseItem.tsx | 39 ++++---- .../components/CourseList/CourseList.tsx | 2 +- .../profile/components/CourseList/index.ts | 3 +- .../components/LoadingPage/LoadingPage.tsx | 24 ----- .../profile/components/LoadingPage/index.ts | 3 - .../UpdateUserContactsForm.tsx | 15 +-- src/pages/profile/components/index.tsx | 98 +++++++++++++++++++ src/pages/profile/profile.tsx | 58 ++++------- .../users/components/UserList/UserList.tsx | 6 ++ src/pages/users/components/UserList/index.ts | 3 +- src/pages/users/users.tsx | 25 ++--- src/stores/UserStore.ts | 4 +- 23 files changed, 264 insertions(+), 194 deletions(-) create mode 100644 src/components/PageElements/index.tsx delete mode 100644 src/pages/profile/components/LoadingPage/LoadingPage.tsx delete mode 100644 src/pages/profile/components/LoadingPage/index.ts create mode 100644 src/pages/profile/components/index.tsx diff --git a/src/components/Buttons/index.tsx b/src/components/Buttons/index.tsx index d67b216..ff1f715 100644 --- a/src/components/Buttons/index.tsx +++ b/src/components/Buttons/index.tsx @@ -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 = props => ( - ); + +export const EditButton: React.FC = props => ( + +); + +export const SaveButton: React.FC = props => ( + +); diff --git a/src/components/ErrorFallback.tsx b/src/components/ErrorFallback.tsx index eb76168..9ec6ab9 100644 --- a/src/components/ErrorFallback.tsx +++ b/src/components/ErrorFallback.tsx @@ -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 ( - - - + Something went wrong diff --git a/src/components/PageElements/index.tsx b/src/components/PageElements/index.tsx new file mode 100644 index 0000000..b5cf7c3 --- /dev/null +++ b/src/components/PageElements/index.tsx @@ -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 = props => { + return {props.children}; +}; + +export const PageHeading: React.FC = props => { + const { heading, description, ...boxProps } = props; + return ( + + {heading} + {description && {description}} + {props.children} + + + ); +}; + +export const PageContent: React.FC = props => {props.children}; + +export const PageFooter: React.FC = props => ( + + + {props.children} + +); diff --git a/src/components/UserBadge/UserBadge.tsx b/src/components/UserBadge/UserBadge.tsx index ee037d0..eeebf09 100644 --- a/src/components/UserBadge/UserBadge.tsx +++ b/src/components/UserBadge/UserBadge.tsx @@ -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 = 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 ( - - {role.name} - - ); - } - - /** - * Cannot find any role for user - */ - return null; + return ( + + {role.name} + + ); }; export default UserBadge; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 2e5ce23..031a917 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -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; } diff --git a/src/pages/404/404.tsx b/src/pages/404/404.tsx index 32b1a4c..53d58a9 100644 --- a/src/pages/404/404.tsx +++ b/src/pages/404/404.tsx @@ -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 = ({ 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 = ({ title }) => { {title} - - - - - - Page not found - - Sorry the page you are looking for could not be found. - - Return to main page - - + + + + + Page not found + + Sorry the page you are looking for could not be found. + + Return to main page + + + ); }; diff --git a/src/pages/auth/auth.tsx b/src/pages/auth/auth.tsx index 94d7bdc..e7f4288 100644 --- a/src/pages/auth/auth.tsx +++ b/src/pages/auth/auth.tsx @@ -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 diff --git a/src/pages/courses/components/CourseStatusTabs/CourseStatusTabs.tsx b/src/pages/courses/components/CourseStatusTabs/CourseStatusTabs.tsx index ac89c72..834f6b6 100644 --- a/src/pages/courses/components/CourseStatusTabs/CourseStatusTabs.tsx +++ b/src/pages/courses/components/CourseStatusTabs/CourseStatusTabs.tsx @@ -23,7 +23,7 @@ const CourseStatusTabs: React.FC = () => { const handleChangeStatus = (status: CourseStatusEnum | undefined) => setCourseStatus(status); return ( - + handleChangeStatus(undefined)}>All handleChangeStatus(CourseStatusEnum.PUBLISHED)}>Published diff --git a/src/pages/courses/courses.tsx b/src/pages/courses/courses.tsx index 05fb8a0..26cf8e1 100644 --- a/src/pages/courses/courses.tsx +++ b/src/pages/courses/courses.tsx @@ -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 = () => { const { userStore: { me }, } = useRootStore(); - const history = useHistory(); if (me === null) return ; return ( - - - - Courses - List of all courses. - - {(me.isAdmin || me.isTeacher) && history.push('/courses/create')} />} - - + + + {(me.isAdmin || me.isTeacher) && } - - + + ); }; diff --git a/src/pages/courses/edit/components/LessonList/LessonList.tsx b/src/pages/courses/edit/components/LessonList/LessonList.tsx index 8ef8946..bfb63f3 100644 --- a/src/pages/courses/edit/components/LessonList/LessonList.tsx +++ b/src/pages/courses/edit/components/LessonList/LessonList.tsx @@ -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; @@ -169,7 +170,7 @@ const LessonList: React.FC = ({ course }) => { - 4 attachments + {lesson.materials_count} attachments @@ -205,10 +206,10 @@ const LessonList: React.FC = ({ course }) => { )} - Total: ({lessons.length}) - + + {lessons.length} lessons + + ; @@ -89,7 +90,7 @@ const StudentTableList: React.FC = ({ render: Row, st - + } /> @@ -104,9 +105,7 @@ const StudentTableList: React.FC = ({ render: Row, st )} - + diff --git a/src/pages/profile/components/CourseList/CourseItem.tsx b/src/pages/profile/components/CourseList/CourseItem.tsx index df05588..109bcc2 100644 --- a/src/pages/profile/components/CourseList/CourseItem.tsx +++ b/src/pages/profile/components/CourseList/CourseItem.tsx @@ -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 = ({ course }) => { return ( - - - - {course.lessons_count} lessons - - - - - - {course.category.title} - - - = ({ course }) => { {course.title} + + + {course.category.title} + + - + + + {course.lessons_count} lessons { - return ( - - - - - - - - - - - - - - - - ); -}; - -export default LoadingPage; diff --git a/src/pages/profile/components/LoadingPage/index.ts b/src/pages/profile/components/LoadingPage/index.ts deleted file mode 100644 index f661969..0000000 --- a/src/pages/profile/components/LoadingPage/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import LoadingPage from './LoadingPage'; - -export default LoadingPage; diff --git a/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx b/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx index 933df5d..704293b 100644 --- a/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx +++ b/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx @@ -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 = ({ con - + } isDisabled={!isDirty} /> ); diff --git a/src/pages/profile/components/index.tsx b/src/pages/profile/components/index.tsx new file mode 100644 index 0000000..d531838 --- /dev/null +++ b/src/pages/profile/components/index.tsx @@ -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 = props => { + return ( + + {props.children} + + ); +}; + +interface IProfileAvatarProps extends BoxProps { + name: string; +} +export const ProfileAvatar: React.FC = props => { + const { colorMode } = useColorMode(); + const isDark = colorMode === 'dark'; + + return ( + + + + ); +}; + +interface IProfileDescriptionProps extends BoxProps { + fullname: string; + email: string; + roles: IUserRole[]; +} +export const ProfileDescription: React.FC = props => { + const { fullname, email, roles, ...boxProps } = props; + return ( + + + + + {fullname} +
+ + {email} + +
+ +
+
+
+ ); +}; + +export const ProfileSignOutButton: React.FC = props => { + const { logout } = useLogout(); + + return ( + + ); +}; + +export const ProfileLoading: React.FC = props => { + return ( + + + + + + + + + + + + + + + + ); +}; diff --git a/src/pages/profile/profile.tsx b/src/pages/profile/profile.tsx index 50da352..da7b680 100644 --- a/src/pages/profile/profile.tsx +++ b/src/pages/profile/profile.tsx @@ -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 = () => { userStore: { me }, } = useRootStore(); const history = useHistory(); - const { logout } = useLogout(); - const { colorMode } = useColorMode(); - if (me === null) return ; + if (me === null) return ; return ( - - My account - Update you contacts information or password here. - - - - - - {me.fullname} - - - {me.email} - - - - - - + + + + + + + Account information @@ -82,7 +62,7 @@ const ProfilePage: React.FC = () => {
- + First name @@ -136,13 +116,11 @@ const ProfilePage: React.FC = () => { )}
-
- - - -
+ + + + + ); }; diff --git a/src/pages/users/components/UserList/UserList.tsx b/src/pages/users/components/UserList/UserList.tsx index c932e0c..aac103b 100644 --- a/src/pages/users/components/UserList/UserList.tsx +++ b/src/pages/users/components/UserList/UserList.tsx @@ -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; limit?: number }; @@ -76,6 +78,10 @@ const UserList: React.FC = ({ render: Item, limit }) => { {deleting && } {users.length !== 0 ? ( <> + + + + Total: ({pagination.total}) diff --git a/src/pages/users/components/UserList/index.ts b/src/pages/users/components/UserList/index.ts index a3c8e07..8ba9cff 100644 --- a/src/pages/users/components/UserList/index.ts +++ b/src/pages/users/components/UserList/index.ts @@ -1,3 +1,4 @@ import UserList from './UserList'; +import UserItem from './UserItem'; -export default UserList; +export { UserList, UserItem }; diff --git a/src/pages/users/users.tsx b/src/pages/users/users.tsx index c7f7b19..727bf5e 100644 --- a/src/pages/users/users.tsx +++ b/src/pages/users/users.tsx @@ -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 = () => { if (me === null) return ; return ( + // TODO: delete page context for better performance - - User management - You can add or delete users on this page. - - - - - + + + - - + + ); }; diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index 6f4713c..1df1df0 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -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, }; });