From c80ef877c56b0b6762e9069205e700ce139479c4 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Mon, 31 Jan 2022 22:41:08 +0300 Subject: [PATCH 1/7] feat: fetch user hook --- src/hooks/queries/user/useFetchUser.ts | 25 ++++++++ src/pages/user/user.tsx | 83 ++++++++++++++++++++++++++ src/routes.config.ts | 8 +++ src/services/UserService.ts | 11 ++++ 4 files changed, 127 insertions(+) create mode 100644 src/hooks/queries/user/useFetchUser.ts create mode 100644 src/pages/user/user.tsx diff --git a/src/hooks/queries/user/useFetchUser.ts b/src/hooks/queries/user/useFetchUser.ts new file mode 100644 index 0000000..3b27c66 --- /dev/null +++ b/src/hooks/queries/user/useFetchUser.ts @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; +import useAsync from '@educt/hooks/useAsync'; +import { IUser } from '@educt/interfaces'; +import { UserServiceInstance } from '@educt/services'; + +const useFetchUser = (id: string) => { + const fetch = async (id: string) => { + try { + const { data } = await UserServiceInstance.fetchUserById(id); + return data; + } catch (error) { + return Promise.reject(error); + } + }; + + const { execute: fetchUserById, ...state } = useAsync>(fetch); + + useEffect(() => { + fetchUserById(id); + }, []); + + return { ...state }; +}; + +export { useFetchUser }; diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx new file mode 100644 index 0000000..0ca51f9 --- /dev/null +++ b/src/pages/user/user.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { PageContent, PageWrapper } from '@educt/components/PageElements'; +import { IPageProps } from '@educt/interfaces'; +import { useFetchUser } from '@educt/hooks/queries/user/useFetchUser'; +import { Redirect, useParams } from 'react-router-dom'; +import LoadingPage from '@educt/components/LoadingPage'; +import { + Box, + Button, + Flex, + Avatar, + Text, + Heading, + useColorModeValue, + Icon, + AvatarBadge, + Divider, +} from '@chakra-ui/react'; +import { MdOutlineMessage } from 'react-icons/md'; +import UserBadge from '@educt/components/UserBadge'; +import { CalendarIcon, RepeatClockIcon } from '@chakra-ui/icons'; + +const UserPage: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const { data: user, error, isLoading } = useFetchUser(id); + const color = useColorModeValue('#E2E8F0', '#2D3748'); + + if (error?.response?.status === 404) return ; + + if (user === null || isLoading) return ; + + return ( + + + + + + + + + + + + + {user.fullname} •  + + + + + + + + + + + Registered: {new Date(user.created_at).toLocaleDateString()} + + + + + + Last login: 3 min ago + + + + + + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam sapiente quisquam vero harum ipsam + officiis rerum. Cum, excepturi nulla. + + + + + + + ); +}; + +export default UserPage; diff --git a/src/routes.config.ts b/src/routes.config.ts index afc8899..da631c7 100644 --- a/src/routes.config.ts +++ b/src/routes.config.ts @@ -14,6 +14,7 @@ import CoursePage from '@educt/pages/course/course'; import EditCoursePage from '@educt/pages/courses/edit/edit'; import ChangePasswordPage from '@educt/pages/profile/change-password/change-password'; import ChangeEmailPage from '@educt/pages/profile/change-email/change-email'; +import UserPage from './pages/user/user'; import { UserRoleEnum } from './enums'; const CoursesPage = lazy(() => import('@educt/pages/courses/courses')); const LessonPage = lazy(() => import('@educt/pages/lesson/lesson')); @@ -126,6 +127,13 @@ export const routes: IRouteOption[] = [ isPrivate: true, roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER], }, + { + path: '/user/:id', + title: 'User', + component: UserPage, + isPrivate: true, + exact: true, + }, { path: '/auth', title: 'Auth', diff --git a/src/services/UserService.ts b/src/services/UserService.ts index 5485a48..c5d999d 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -45,6 +45,17 @@ class UserService { return result.data; } + /** + * Fetch user by id + * + * @param id User id + * @returns Users + */ + public async fetchUserById(id: string): Promise> { + const result = await this.api.get(`/v1/users/${id}`); + return result.data; + } + /** * Create new user * From 20066ec1ba0735aa40305772d3a01ed647d7e316 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Tue, 1 Feb 2022 21:49:05 +0300 Subject: [PATCH 2/7] refactor: user profile component decomposition --- src/components/UserProfile/Avatar/Avatar.tsx | 20 +++++ .../UserProfile/Avatar/ChatButton.tsx | 19 ++++ src/components/UserProfile/Avatar/index.ts | 13 +++ src/components/UserProfile/Info/About.tsx | 16 ++++ src/components/UserProfile/Info/Details.tsx | 30 +++++++ src/components/UserProfile/Info/Heading.tsx | 19 ++++ src/components/UserProfile/Info/Info.tsx | 12 +++ src/components/UserProfile/Info/index.ts | 21 +++++ src/components/UserProfile/index.tsx | 29 +++++++ src/pages/user/user.tsx | 87 +++++-------------- 10 files changed, 203 insertions(+), 63 deletions(-) create mode 100644 src/components/UserProfile/Avatar/Avatar.tsx create mode 100644 src/components/UserProfile/Avatar/ChatButton.tsx create mode 100644 src/components/UserProfile/Avatar/index.ts create mode 100644 src/components/UserProfile/Info/About.tsx create mode 100644 src/components/UserProfile/Info/Details.tsx create mode 100644 src/components/UserProfile/Info/Heading.tsx create mode 100644 src/components/UserProfile/Info/Info.tsx create mode 100644 src/components/UserProfile/Info/index.ts create mode 100644 src/components/UserProfile/index.tsx diff --git a/src/components/UserProfile/Avatar/Avatar.tsx b/src/components/UserProfile/Avatar/Avatar.tsx new file mode 100644 index 0000000..e1b507f --- /dev/null +++ b/src/components/UserProfile/Avatar/Avatar.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Flex, Avatar as ChakraAvatar, AvatarBadge as ChakraAvatarBadge, useColorModeValue } from '@chakra-ui/react'; +import type { IAvatarProps } from '.'; + +const Avatar: React.FC = props => { + const { name, isOnline } = props; + const onlineColor = isOnline ? 'green.500' : 'red.500'; + const borderColor = useColorModeValue('#E2E8F0', '#2D3748'); + + return ( + + + + + {props.children} + + ); +}; + +export { Avatar }; diff --git a/src/components/UserProfile/Avatar/ChatButton.tsx b/src/components/UserProfile/Avatar/ChatButton.tsx new file mode 100644 index 0000000..92f0158 --- /dev/null +++ b/src/components/UserProfile/Avatar/ChatButton.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Button, ButtonProps } from '@chakra-ui/react'; +import { MdOutlineMessage } from 'react-icons/md'; +import { useHistory } from 'react-router-dom'; +import type { IChatButtonProps } from '.'; + +const ChatButton: React.FC = props => { + const { userid } = props; + const history = useHistory(); + const handleChat = () => history.push(`/chat/${userid}`); + + return ( + + ); +}; + +export { ChatButton }; diff --git a/src/components/UserProfile/Avatar/index.ts b/src/components/UserProfile/Avatar/index.ts new file mode 100644 index 0000000..e2e6c6b --- /dev/null +++ b/src/components/UserProfile/Avatar/index.ts @@ -0,0 +1,13 @@ +import { Avatar } from './Avatar'; +import { ChatButton } from './ChatButton'; + +export interface IAvatarProps { + name: string; + isOnline: boolean; +} + +export interface IChatButtonProps { + userid: string; +} + +export { Avatar, ChatButton }; diff --git a/src/components/UserProfile/Info/About.tsx b/src/components/UserProfile/Info/About.tsx new file mode 100644 index 0000000..51457c4 --- /dev/null +++ b/src/components/UserProfile/Info/About.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Box, Text } from '@chakra-ui/react'; +import type { IAboutProps } from '.'; + +const About: React.FC = () => { + return ( + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam sapiente quisquam vero harum ipsam officiis + rerum. Cum, excepturi nulla. + + + ); +}; + +export { About }; diff --git a/src/components/UserProfile/Info/Details.tsx b/src/components/UserProfile/Info/Details.tsx new file mode 100644 index 0000000..2881764 --- /dev/null +++ b/src/components/UserProfile/Info/Details.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Flex, Text, Icon, Divider } from '@chakra-ui/react'; +import { CalendarIcon, RepeatClockIcon } from '@chakra-ui/icons'; +import type { IDetailsProps } from '.'; + +const Details: React.FC = ({ registered, lastLogin }) => { + return ( + <> + + + + + + Joined: {new Date(registered).toLocaleDateString()} + + + + + + Last login: {lastLogin} + + + + + + + ); +}; + +export { Details }; diff --git a/src/components/UserProfile/Info/Heading.tsx b/src/components/UserProfile/Info/Heading.tsx new file mode 100644 index 0000000..85778f9 --- /dev/null +++ b/src/components/UserProfile/Info/Heading.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import UserBadge from '@educt/components/UserBadge'; +import { Flex, Box, Heading as ChakraHeading } from '@chakra-ui/react'; +import type { IHeadingProps } from '.'; + +const Heading: React.FC = ({ fullname, roles }) => { + return ( + + + {fullname} •  + + + + + + ); +}; + +export { Heading }; diff --git a/src/components/UserProfile/Info/Info.tsx b/src/components/UserProfile/Info/Info.tsx new file mode 100644 index 0000000..6faeb84 --- /dev/null +++ b/src/components/UserProfile/Info/Info.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Box } from '@chakra-ui/react'; + +const Info: React.FC = props => { + return ( + + {props.children} + + ); +}; + +export { Info }; diff --git a/src/components/UserProfile/Info/index.ts b/src/components/UserProfile/Info/index.ts new file mode 100644 index 0000000..50ad6c1 --- /dev/null +++ b/src/components/UserProfile/Info/index.ts @@ -0,0 +1,21 @@ +import { IUser } from '@educt/interfaces'; +import { Info } from './Info'; +import { Heading } from './Heading'; +import { Details } from './Details'; +import { About } from './About'; + +export interface IHeadingProps { + fullname: string; + roles: IUser['roles']; +} + +export interface IDetailsProps { + registered: string; + lastLogin: string; +} + +export interface IAboutProps { + about: string; +} + +export { Info, Heading, Details, About }; diff --git a/src/components/UserProfile/index.tsx b/src/components/UserProfile/index.tsx new file mode 100644 index 0000000..399c265 --- /dev/null +++ b/src/components/UserProfile/index.tsx @@ -0,0 +1,29 @@ +import React, { createContext } from 'react'; +import { BoxProps, Flex } from '@chakra-ui/react'; +import { IUser } from '@educt/interfaces'; +import { Avatar, ChatButton, IAvatarProps, IChatButtonProps } from './Avatar'; +import { About, Details, Heading, IAboutProps, IDetailsProps, IHeadingProps, Info } from './Info'; + +interface IUserProfileComposition { + Avatar: React.FC; + ChatButton: React.FC; + Info: React.FC; + Heading: React.FC; + Details: React.FC; + About: React.FC; +} + +export const UserProfileContext = createContext(undefined); + +const UserProfile: React.FC & IUserProfileComposition = props => { + return {props.children}; +}; + +UserProfile.Avatar = Avatar; +UserProfile.ChatButton = ChatButton; +UserProfile.Info = Info; +UserProfile.Heading = Heading; +UserProfile.Details = Details; +UserProfile.About = About; + +export { UserProfile }; diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx index 0ca51f9..8660c78 100644 --- a/src/pages/user/user.tsx +++ b/src/pages/user/user.tsx @@ -1,29 +1,19 @@ import React from 'react'; -import { PageContent, PageWrapper } from '@educt/components/PageElements'; -import { IPageProps } from '@educt/interfaces'; -import { useFetchUser } from '@educt/hooks/queries/user/useFetchUser'; -import { Redirect, useParams } from 'react-router-dom'; +import { Redirect } from 'react-router-dom'; import LoadingPage from '@educt/components/LoadingPage'; -import { - Box, - Button, - Flex, - Avatar, - Text, - Heading, - useColorModeValue, - Icon, - AvatarBadge, - Divider, -} from '@chakra-ui/react'; -import { MdOutlineMessage } from 'react-icons/md'; -import UserBadge from '@educt/components/UserBadge'; -import { CalendarIcon, RepeatClockIcon } from '@chakra-ui/icons'; +import { PageContent, PageWrapper } from '@educt/components/PageElements'; +import { UserProfile } from '@educt/components/UserProfile'; +import type { IPageProps } from '@educt/interfaces'; + +/** + * Hooks + */ +import { useParams } from 'react-router-dom'; +import { useFetchUser } from '@educt/hooks/queries/user/useFetchUser'; const UserPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const { data: user, error, isLoading } = useFetchUser(id); - const color = useColorModeValue('#E2E8F0', '#2D3748'); if (error?.response?.status === 404) return ; @@ -32,49 +22,20 @@ const UserPage: React.FC = () => { return ( - - - - - - - - - - - {user.fullname} •  - - - - - - - - - - - Registered: {new Date(user.created_at).toLocaleDateString()} - - - - - - Last login: 3 min ago - - - - - - - - Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam sapiente quisquam vero harum ipsam - officiis rerum. Cum, excepturi nulla. - - - - + + + + + + + + + + ); From 7f3eabe3e46d4be38220ec3cf9266d984493bbe5 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Wed, 2 Feb 2022 17:33:14 +0300 Subject: [PATCH 3/7] feat: render last_login & about on user page --- src/components/UserProfile/Info/About.tsx | 11 ++++++++--- src/components/UserProfile/Info/Details.tsx | 3 ++- src/components/UserProfile/Info/index.ts | 4 ++-- src/interfaces/index.ts | 2 ++ src/pages/user/user.tsx | 8 ++------ 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/components/UserProfile/Info/About.tsx b/src/components/UserProfile/Info/About.tsx index 51457c4..7f4fe5e 100644 --- a/src/components/UserProfile/Info/About.tsx +++ b/src/components/UserProfile/Info/About.tsx @@ -2,12 +2,17 @@ import React from 'react'; import { Box, Text } from '@chakra-ui/react'; import type { IAboutProps } from '.'; -const About: React.FC = () => { +const About: React.FC = ({ about }) => { return ( - Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam sapiente quisquam vero harum ipsam officiis - rerum. Cum, excepturi nulla. + {about ? ( + about + ) : ( + + No description provided + + )} ); diff --git a/src/components/UserProfile/Info/Details.tsx b/src/components/UserProfile/Info/Details.tsx index 2881764..b856a8a 100644 --- a/src/components/UserProfile/Info/Details.tsx +++ b/src/components/UserProfile/Info/Details.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import moment from 'moment'; import { Flex, Text, Icon, Divider } from '@chakra-ui/react'; import { CalendarIcon, RepeatClockIcon } from '@chakra-ui/icons'; import type { IDetailsProps } from '.'; @@ -17,7 +18,7 @@ const Details: React.FC = ({ registered, lastLogin }) => { - Last login: {lastLogin} + Last login: {lastLogin ? `${moment(lastLogin).fromNow()}` : 'Never'} diff --git a/src/components/UserProfile/Info/index.ts b/src/components/UserProfile/Info/index.ts index 50ad6c1..7ce80d6 100644 --- a/src/components/UserProfile/Info/index.ts +++ b/src/components/UserProfile/Info/index.ts @@ -11,11 +11,11 @@ export interface IHeadingProps { export interface IDetailsProps { registered: string; - lastLogin: string; + lastLogin: IUser['last_login']; } export interface IAboutProps { - about: string; + about: IUser['about']; } export { Info, Heading, Details, About }; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index cb74b60..f674a5a 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -116,6 +116,8 @@ export interface IUser { first_name: string; last_name: string; fullname: string; + about: string | null; + last_login: string | null; email: string; roles: IUserRole[]; contacts: IUserContacts | null; diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx index 8660c78..ec006a8 100644 --- a/src/pages/user/user.tsx +++ b/src/pages/user/user.tsx @@ -28,12 +28,8 @@ const UserPage: React.FC = () => { - - + + From a14ed5eff9db367c589733ccbdd29a4db2195688 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Wed, 2 Feb 2022 19:03:44 +0300 Subject: [PATCH 4/7] refactor: page --- .../AddStudentsModal/AddSudentsModal.tsx | 2 +- src/components/PageElements/index.tsx | 24 ++++++++----- src/components/UserProfile/Avatar/Avatar.tsx | 2 +- src/components/UserProfile/Avatar/index.ts | 2 +- src/components/UserProfile/index.tsx | 15 ++++---- src/pages/404/404.tsx | 10 +++--- src/pages/categories/categories.tsx | 18 ++++++---- src/pages/course/course.tsx | 10 +++--- src/pages/courses/courses.tsx | 14 ++++---- .../courses/create-lesson/create-lesson.tsx | 12 +++---- src/pages/courses/create/create.tsx | 13 ++++--- src/pages/courses/edit/edit.tsx | 14 ++++---- src/pages/lesson/lesson.tsx | 14 ++++---- src/pages/lessons/edit-lesson/edit-lesson.tsx | 14 ++++---- src/pages/main/components/index.tsx | 2 +- src/pages/main/main.tsx | 20 ++++++----- src/pages/messages/messages.tsx | 9 ++--- src/pages/profile/components/index.tsx | 10 ------ src/pages/profile/profile.tsx | 34 ++++++++++--------- src/pages/user/user.tsx | 28 ++++++++++----- src/pages/users/users.tsx | 12 +++---- src/stores/UserStore.ts | 19 ++++++++++- 22 files changed, 167 insertions(+), 131 deletions(-) diff --git a/src/components/Modals/AddStudentsModal/AddSudentsModal.tsx b/src/components/Modals/AddStudentsModal/AddSudentsModal.tsx index cd2164e..4fddfb1 100644 --- a/src/components/Modals/AddStudentsModal/AddSudentsModal.tsx +++ b/src/components/Modals/AddStudentsModal/AddSudentsModal.tsx @@ -53,7 +53,7 @@ const AddStudentsModal: React.FC = ({ icon={MdGroup} heading='Add new students' description='Attach new students to current course.' - confirmText='Add students' + confirmText='Add' isOpen={isOpen} onClose={onClose} onProceed={handleAttachStudents} diff --git a/src/components/PageElements/index.tsx b/src/components/PageElements/index.tsx index 1d06fc4..b11f8c6 100644 --- a/src/components/PageElements/index.tsx +++ b/src/components/PageElements/index.tsx @@ -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; + Content: React.FC; + Footer: React.FC; +} + +const Page: React.FC & IPageComposition = props => { + return {props.children}; +}; + +interface IPageHeadingProps { headingPrefix?: JSX.Element; heading: string; description?: string; } -export const PageWrapper: React.FC = props => { - return {props.children}; -}; - -export const PageHeading: React.FC = props => { +Page.Heading = props => { const { headingPrefix, heading, description, ...boxProps } = props; return ( @@ -26,15 +32,17 @@ export const PageHeading: React.FC = props => { ); }; -export const PageContent: React.FC = props => ( +Page.Content = props => ( {props.children} ); -export const PageFooter: React.FC = props => ( +Page.Footer = props => ( {props.children} ); + +export { Page }; diff --git a/src/components/UserProfile/Avatar/Avatar.tsx b/src/components/UserProfile/Avatar/Avatar.tsx index e1b507f..cc6ba4d 100644 --- a/src/components/UserProfile/Avatar/Avatar.tsx +++ b/src/components/UserProfile/Avatar/Avatar.tsx @@ -10,7 +10,7 @@ const Avatar: React.FC = props => { return ( - + {isOnline && } {props.children} diff --git a/src/components/UserProfile/Avatar/index.ts b/src/components/UserProfile/Avatar/index.ts index e2e6c6b..191590b 100644 --- a/src/components/UserProfile/Avatar/index.ts +++ b/src/components/UserProfile/Avatar/index.ts @@ -3,7 +3,7 @@ import { ChatButton } from './ChatButton'; export interface IAvatarProps { name: string; - isOnline: boolean; + isOnline?: boolean | undefined; } export interface IChatButtonProps { diff --git a/src/components/UserProfile/index.tsx b/src/components/UserProfile/index.tsx index 399c265..43b705f 100644 --- a/src/components/UserProfile/index.tsx +++ b/src/components/UserProfile/index.tsx @@ -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; } -export const UserProfileContext = createContext(undefined); - -const UserProfile: React.FC & IUserProfileComposition = props => { - return {props.children}; +const UserProfile: React.FC & IUserProfileComposition = props => { + return ( + + {props.children} + + ); }; UserProfile.Avatar = Avatar; diff --git a/src/pages/404/404.tsx b/src/pages/404/404.tsx index 2d67e41..7e9bf88 100644 --- a/src/pages/404/404.tsx +++ b/src/pages/404/404.tsx @@ -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 = ({ title }) => { {title} - - + + Page not found @@ -38,8 +38,8 @@ const NotFoundPage: React.FC = ({ title }) => { Return to main page - - + + ); }; diff --git a/src/pages/categories/categories.tsx b/src/pages/categories/categories.tsx index 676047c..7d9f47f 100644 --- a/src/pages/categories/categories.tsx +++ b/src/pages/categories/categories.tsx @@ -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 = () => { if (isLoading || categories === null) return ; return ( - - - + + + - - + + ); }; diff --git a/src/pages/course/course.tsx b/src/pages/course/course.tsx index 3888fb2..38dd412 100644 --- a/src/pages/course/course.tsx +++ b/src/pages/course/course.tsx @@ -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 = () => { const handleEditCourse = () => history.push(`/courses/edit/${course.id}`); return ( - + {course.title} @@ -64,7 +64,7 @@ const CoursePage: React.FC = () => { /> - + @@ -158,8 +158,8 @@ const CoursePage: React.FC = () => { - - + + ); }; diff --git a/src/pages/courses/courses.tsx b/src/pages/courses/courses.tsx index c678e1d..61ca3ce 100644 --- a/src/pages/courses/courses.tsx +++ b/src/pages/courses/courses.tsx @@ -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 = () => { if (me === null) return ; return ( - - + + {(me.isAdmin || me.isTeacher) && } - - + + - - + + ); }; diff --git a/src/pages/courses/create-lesson/create-lesson.tsx b/src/pages/courses/create-lesson/create-lesson.tsx index bd19b90..8a95c90 100644 --- a/src/pages/courses/create-lesson/create-lesson.tsx +++ b/src/pages/courses/create-lesson/create-lesson.tsx @@ -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 = () => { if (!course || isLoading) return ; return ( - - - + + + - - + + ); }; diff --git a/src/pages/courses/create/create.tsx b/src/pages/courses/create/create.tsx index d0593b7..20f655c 100644 --- a/src/pages/courses/create/create.tsx +++ b/src/pages/courses/create/create.tsx @@ -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 = () => { return ( - - + } heading='Course Creation' description='Fill in the required fields to create a new course' /> - + - - + + ); }; diff --git a/src/pages/courses/edit/edit.tsx b/src/pages/courses/edit/edit.tsx index 5f31d85..0bcea87 100644 --- a/src/pages/courses/edit/edit.tsx +++ b/src/pages/courses/edit/edit.tsx @@ -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 = () => { if (!course || isLoading) return ; return ( - + Editing {course.title} - } heading='Course editor' description={course.title} @@ -58,8 +58,8 @@ const EditCoursePage: React.FC = () => { - - + + @@ -91,8 +91,8 @@ const EditCoursePage: React.FC = () => { - - + + ); }; diff --git a/src/pages/lesson/lesson.tsx b/src/pages/lesson/lesson.tsx index 3eb9ee9..d78d318 100644 --- a/src/pages/lesson/lesson.tsx +++ b/src/pages/lesson/lesson.tsx @@ -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 = () => { if (me === null) return ; return ( - + {lesson.title} - + ~{moment.duration(lesson.duration, 'minutes').humanize()} {(me.isAdmin || me.isTeacher) && } - - + + @@ -270,8 +270,8 @@ const LessonPage: React.FC = () => { - - + + ); }; diff --git a/src/pages/lessons/edit-lesson/edit-lesson.tsx b/src/pages/lessons/edit-lesson/edit-lesson.tsx index e04896f..2d14b3e 100644 --- a/src/pages/lessons/edit-lesson/edit-lesson.tsx +++ b/src/pages/lessons/edit-lesson/edit-lesson.tsx @@ -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 = () => { if (!lesson || isLoading) return ; return ( - - - + + + - - + + ); }; diff --git a/src/pages/main/components/index.tsx b/src/pages/main/components/index.tsx index d16e58b..585a174 100644 --- a/src/pages/main/components/index.tsx +++ b/src/pages/main/components/index.tsx @@ -34,7 +34,7 @@ export const CourseCard: React.FC = props => = props => ( ); -export const CourseCardTitle: React.FC = props => ; +export const CourseCardTitle: React.FC = props => ; export const CourseCardCategory: React.FC = props => ( diff --git a/src/pages/main/main.tsx b/src/pages/main/main.tsx index 70700e8..ceb9684 100644 --- a/src/pages/main/main.tsx +++ b/src/pages/main/main.tsx @@ -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 = () => { if (me === null || courses === null || stat === null) return ; return ( - - - + + + Online @@ -121,8 +125,8 @@ const MainPage: React.FC = () => { - - + + ); }; diff --git a/src/pages/messages/messages.tsx b/src/pages/messages/messages.tsx index ba75b29..b78ffdf 100644 --- a/src/pages/messages/messages.tsx +++ b/src/pages/messages/messages.tsx @@ -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 = () => { return ( - - Messages page - + + + ); }; diff --git a/src/pages/profile/components/index.tsx b/src/pages/profile/components/index.tsx index 1f89cb7..882843c 100644 --- a/src/pages/profile/components/index.tsx +++ b/src/pages/profile/components/index.tsx @@ -69,16 +69,6 @@ export const ProfileDescription: React.FC = props => { ); }; -export const ProfileSignOutButton: React.FC = () => { - 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 286998b..d8bb62f 100644 --- a/src/pages/profile/profile.tsx +++ b/src/pages/profile/profile.tsx @@ -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 = () => { if (me === null) return ; return ( - - - - - - - + + + + + + + + + + + Account information @@ -116,12 +121,9 @@ const ProfilePage: React.FC = () => { )} - - - - - + + ); }; -export default ProfilePage; +export default observer(ProfilePage); diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx index ec006a8..57fe9fe 100644 --- a/src/pages/user/user.tsx +++ b/src/pages/user/user.tsx @@ -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 = () => { - 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 ; - if (user === null || isLoading) return ; + if (user === null || me === null || isLoading) return ; + + /** + * Redirect to profile page + */ + if (me.id === params.id) return ; return ( - - + + @@ -32,9 +42,9 @@ const UserPage: React.FC = () => { - - + + ); }; -export default UserPage; +export default observer(UserPage); diff --git a/src/pages/users/users.tsx b/src/pages/users/users.tsx index 727bf5e..1615720 100644 --- a/src/pages/users/users.tsx +++ b/src/pages/users/users.tsx @@ -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 = () => { return ( // TODO: delete page context for better performance - - - + + + - - + + ); }; diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index aef29e3..8d7ca7f 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -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, From ab662a50f6dfb3e0b56447a7d85fbf222d5d7080 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Wed, 2 Feb 2022 21:32:44 +0300 Subject: [PATCH 5/7] refactor: profile layout --- src/components/UserProfile/Base/Base.tsx | 12 ++ src/components/UserProfile/Base/index.ts | 3 + src/components/UserProfile/Settings/Entry.tsx | 8 + .../UserProfile/Settings/EntryContent.tsx | 12 ++ .../UserProfile/Settings/Settings.tsx | 31 +++ src/components/UserProfile/Settings/Title.tsx | 18 ++ src/components/UserProfile/Settings/index.ts | 3 + src/components/UserProfile/index.tsx | 9 +- src/pages/profile/profile.tsx | 198 +++++++++++------- src/pages/user/user.tsx | 18 +- 10 files changed, 224 insertions(+), 88 deletions(-) create mode 100644 src/components/UserProfile/Base/Base.tsx create mode 100644 src/components/UserProfile/Base/index.ts create mode 100644 src/components/UserProfile/Settings/Entry.tsx create mode 100644 src/components/UserProfile/Settings/EntryContent.tsx create mode 100644 src/components/UserProfile/Settings/Settings.tsx create mode 100644 src/components/UserProfile/Settings/Title.tsx create mode 100644 src/components/UserProfile/Settings/index.ts diff --git a/src/components/UserProfile/Base/Base.tsx b/src/components/UserProfile/Base/Base.tsx new file mode 100644 index 0000000..09e6f7c --- /dev/null +++ b/src/components/UserProfile/Base/Base.tsx @@ -0,0 +1,12 @@ +import { Flex, FlexProps } from '@chakra-ui/react'; +import React from 'react'; + +const Base: React.FC = props => { + return ( + + {props.children} + + ); +}; + +export { Base }; diff --git a/src/components/UserProfile/Base/index.ts b/src/components/UserProfile/Base/index.ts new file mode 100644 index 0000000..5dfb06a --- /dev/null +++ b/src/components/UserProfile/Base/index.ts @@ -0,0 +1,3 @@ +import { Base } from './Base'; + +export { Base }; diff --git a/src/components/UserProfile/Settings/Entry.tsx b/src/components/UserProfile/Settings/Entry.tsx new file mode 100644 index 0000000..dda09f5 --- /dev/null +++ b/src/components/UserProfile/Settings/Entry.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { Flex } from '@chakra-ui/react'; + +const Entry: React.FC = props => { + return {props.children}; +}; + +export { Entry }; diff --git a/src/components/UserProfile/Settings/EntryContent.tsx b/src/components/UserProfile/Settings/EntryContent.tsx new file mode 100644 index 0000000..568ca77 --- /dev/null +++ b/src/components/UserProfile/Settings/EntryContent.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Flex } from '@chakra-ui/react'; + +const EntryContent: React.FC = props => { + return ( + + {props.children} + + ); +}; + +export { EntryContent }; diff --git a/src/components/UserProfile/Settings/Settings.tsx b/src/components/UserProfile/Settings/Settings.tsx new file mode 100644 index 0000000..fa9f04f --- /dev/null +++ b/src/components/UserProfile/Settings/Settings.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Box, BoxProps, Heading as ChakraHeading, Divider, Stack, StackDivider } from '@chakra-ui/react'; +import { Title, ITitleProps } from './Title'; +import { Entry } from './Entry'; +import { EntryContent } from './EntryContent'; + +export interface ISettingsComposition { + Title: React.FC; + Entry: React.FC; + EntryContent: React.FC; +} + +const Settings: React.FC & ISettingsComposition = props => { + return ( + + + Account Settings + + + } spacing={'5'}> + {props.children} + + + ); +}; + +Settings.Title = Title; +Settings.Entry = Entry; +Settings.EntryContent = EntryContent; + +export { Settings }; diff --git a/src/components/UserProfile/Settings/Title.tsx b/src/components/UserProfile/Settings/Title.tsx new file mode 100644 index 0000000..43d5fce --- /dev/null +++ b/src/components/UserProfile/Settings/Title.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { Box, BoxProps, Heading } from '@chakra-ui/react'; + +export interface ITitleProps { + title?: string | undefined; +} + +const Title: React.FC = props => { + return ( + + + {props.title} + + + ); +}; + +export { Title }; diff --git a/src/components/UserProfile/Settings/index.ts b/src/components/UserProfile/Settings/index.ts new file mode 100644 index 0000000..50e0e7d --- /dev/null +++ b/src/components/UserProfile/Settings/index.ts @@ -0,0 +1,3 @@ +import { Settings } from './Settings'; + +export { Settings }; diff --git a/src/components/UserProfile/index.tsx b/src/components/UserProfile/index.tsx index 43b705f..ac7ee50 100644 --- a/src/components/UserProfile/index.tsx +++ b/src/components/UserProfile/index.tsx @@ -1,30 +1,37 @@ import React from 'react'; import { BoxProps, Flex, FlexProps } from '@chakra-ui/react'; +import { Base } from './Base'; import { Avatar, ChatButton, IAvatarProps, IChatButtonProps } from './Avatar'; import { About, Details, Heading, IAboutProps, IDetailsProps, IHeadingProps, Info } from './Info'; +import { Settings } from './Settings'; +import { ISettingsComposition } from './Settings/Settings'; interface IUserProfileComposition { + Base: React.FC; Avatar: React.FC; ChatButton: React.FC; Info: React.FC; Heading: React.FC; Details: React.FC; About: React.FC; + Settings: React.FC & ISettingsComposition; } const UserProfile: React.FC & IUserProfileComposition = props => { return ( - + {props.children} ); }; +UserProfile.Base = Base; UserProfile.Avatar = Avatar; UserProfile.ChatButton = ChatButton; UserProfile.Info = Info; UserProfile.Heading = Heading; UserProfile.Details = Details; UserProfile.About = About; +UserProfile.Settings = Settings; export { UserProfile }; diff --git a/src/pages/profile/profile.tsx b/src/pages/profile/profile.tsx index d8bb62f..3a75bb2 100644 --- a/src/pages/profile/profile.tsx +++ b/src/pages/profile/profile.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { - Tabs, - TabList, - Tab, - TabPanels, - TabPanel, - Button, - FormControl, - FormHelperText, - Input, InputGroup, InputRightElement, + Flex, + Switch, + FormControl, + FormLabel, + Input, + Textarea, + FormHelperText, + Select, + Button, } from '@chakra-ui/react'; import { Box, Stack } from '@chakra-ui/layout'; @@ -25,15 +25,13 @@ import { IPageProps } from '@educt/interfaces'; 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'; /** * Hooks */ -import { useHistory } from 'react-router-dom'; import { useRootStore } from '@educt/hooks/useRootStore'; import { observer } from 'mobx-react'; +import { MdOutlineSave } from 'react-icons/md'; /** * Profile page @@ -42,7 +40,6 @@ const ProfilePage: React.FC = () => { const { userStore: { me }, } = useRootStore(); - const history = useHistory(); if (me === null) return ; @@ -51,76 +48,119 @@ const ProfilePage: React.FC = () => { - - - - - - - - - - Account information - My contacts - {me.isStudent && Available courses} - - - -
- - - - First name - - + + + + + + + + - - Last name - - + + {/* Personal Info */} + + + + + + Fullname + + + + Email + + + + + + + + + About +