feat: added layout

This commit is contained in:
Sergey Yarkov 2021-12-17 21:55:28 +03:00
parent f8ae2107eb
commit 63082e50c2
9 changed files with 261 additions and 14 deletions

View File

@ -10,15 +10,13 @@ import { CourseServiceInstance } from '@educt/services';
import { useEffect } from 'react';
import useAsync from '../../useAsync';
type CourseResultDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
const useFetchCourse = (id: string) => {
const fetch = async (id: string) => {
const { data } = await CourseServiceInstance.fetchById(id);
return data;
};
const { execute: fetchCourseById, ...state } = useAsync<CourseResultDataType, Parameters<typeof fetch>>(fetch);
const { execute: fetchCourseById, ...state } = useAsync<ICourse, Parameters<typeof fetch>>(fetch);
useEffect(() => {
fetchCourseById(id);

View File

@ -1,7 +1,8 @@
import { ChakraProvider, ColorModeScript, theme } from '@chakra-ui/react';
import * as React from 'react';
import ReactDOM from 'react-dom';
import { ChakraProvider, ColorModeScript } from '@chakra-ui/react';
import App from './App';
import { theme } from './theme';
import { RootStoreProvider } from './providers';
ReactDOM.render(

View File

@ -21,7 +21,7 @@ const NotFoundPage: React.FC<IPageProps> = ({ title }) => {
useEffect(() => {
toast({ title: `Page not found`, duration: 10000, isClosable: true, status: 'warning' });
});
}, []);
return (
<>

View File

@ -41,11 +41,8 @@ const AuthPage: React.FC<IPageProps> = ({ title }) => {
<Helmet>
<title>{title}</title>
</Helmet>
<Box maxW='md' mx='auto'>
<Logo mx='auto' h='14' mb={{ base: '10', md: '20' }} />
<Heading mb='8' textAlign='center' size='xl' fontWeight='extrabold'>
Login to your account
</Heading>
<Box maxW='md' mx='auto' mt='20'>
<Logo mx='auto' h='14' mb={{ base: '10', md: '10' }} />
<Card>
<AuthForm />
</Card>

View File

@ -84,7 +84,7 @@ const AuthForm: React.FC = () => {
<Button
type='submit'
colorScheme='blue'
size='lg'
size='md'
fontSize='md'
loadingText='Logging in...'
isLoading={isLoading}

221
src/pages/course/course.tsx Normal file
View File

@ -0,0 +1,221 @@
import React from 'react';
import { Redirect, useHistory } from 'react-router-dom';
import {
Flex,
Box,
Text,
Tag,
Heading,
Icon,
Button,
Grid,
GridItem,
Image,
Divider,
AvatarGroup,
LinkBox,
LinkOverlay,
Avatar,
useColorModeValue,
} from '@chakra-ui/react';
import { MdThumbUpOffAlt, MdPlayCircleOutline, MdOutlineGroup, MdOutlineVideoLibrary } from 'react-icons/md';
import { Tabs, TabList, TabPanels, Tab, TabPanel, Stack } from '@chakra-ui/react';
import { Link as ReactRouterLink } from 'react-router-dom';
/**
* Types
*/
import { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import { PageWrapper, PageContent } from '@educt/components/PageElements';
/**
* Hooks
*/
import { useParams } from 'react-router-dom';
import { useFetchCourse } from '@educt/hooks/queries';
import LoadingPage from '@educt/components/LoadingPage';
import moment from 'moment';
const CoursePage: React.FC<IPageProps> = () => {
const params = useParams<{ id: string }>();
const { error, data: course, isLoading } = useFetchCourse(params.id);
const history = useHistory();
const color = useColorModeValue('gray.50', 'gray.700');
if (error?.response?.status === 404) {
return <Redirect to='/404' />;
}
if (!course || isLoading) return <LoadingPage />;
const onWatchCourse = () => history.push(`/lesson/${course.lessons[0].id}`);
return (
<PageWrapper maxW='1200px'>
<Flex justifyContent='space-between' flexDir={{ base: 'column', xl: 'row' }}>
<Box>
<Heading as='h1' fontSize='3xl' mr='4'>
{course.title}
</Heading>
<Flex mt='3' alignItems={'center'}>
<Text fontSize='sm'>{course.teacher.fullname}</Text>&nbsp;&bull;&nbsp;
<Tag size='sm' variant='solid' bg={course.category.color?.hex}>
{course.category.title}
</Tag>
</Flex>
</Box>
<Box mt={{ base: '2', xl: '0' }}>
<Flex alignItems={'center'}>
<Icon as={MdOutlineVideoLibrary} />
<Text as='span' fontSize={'sm'} ml='1' mr='4'>
{course.lessons_count}
</Text>
<Icon as={MdOutlineGroup} />
<Text as='span' fontSize={'sm'} ml='1' mr='2'>
{course.students_count}
</Text>
<Button leftIcon={<MdThumbUpOffAlt />} variant='ghost'>
{course.likes_count}
</Button>
</Flex>
</Box>
</Flex>
<PageContent mt={{ base: '5', sm: '2' }}>
<Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }} gap={'7'}>
<GridItem>
<Box>
<Flex justifyContent={'space-between'} alignItems={'center'} mb='2'>
<Text fontSize={'sm'}>Last update: {new Date(course.updated_at).toLocaleDateString()}</Text>
<Button onClick={onWatchCourse} leftIcon={<MdPlayCircleOutline />} size='sm' variant={'outline'}>
Watch
</Button>
</Flex>
<Image
bg={course.color?.hex}
borderRadius={'lg'}
objectFit='cover'
w='full'
h={{ base: '300px', sm: '300px', md: '300px', lg: '400px' }}
src={course.image?.url}
/>
</Box>
<Box mt='4'>
<Tabs>
<TabList>
<Tab>About</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Box mt='6'>
<Heading as='h2' fontSize='2xl' fontWeight={'medium'}>
About this course
</Heading>
<Text mt='2'>{course.description}</Text>
</Box>
{course.students.length !== 0 ? (
<Flex justifyContent={'flex-end'} alignItems={'center'} mt='5'>
<Text fontSize={'sm'} mr='2'>
Students:
</Text>
<AvatarGroup size='sm' max={2}>
{course.students.map(student => (
<Avatar key={student.id} name={student.fullname} />
))}
</AvatarGroup>
</Flex>
) : (
<Box textAlign={'right'}>
<Text fontSize={'sm'} userSelect={'none'} color='gray.500'>
0 students
</Text>
</Box>
)}
</TabPanel>
</TabPanels>
</Tabs>
</Box>
<Divider mt='5' />
</GridItem>
<GridItem borderRadius={'lg'} minWidth={'290px'} h='550px'>
<Box>
<Text fontSize={'2xl'} fontWeight={'medium'}>
What you&apos;ll learn.
</Text>
<Text fontSize={'sm'} mt='2'>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Iste temporibus harum eos praesentium hic,
sunt corporis deserunt impedit natus consequuntur?
</Text>
</Box>
<Divider mt='5' />
<Box mt='4'>
<Flex alignItems={'center'} justifyContent={'space-between'}>
<Text fontSize={'2xl'} fontWeight={'medium'}>
Content
</Text>
<Text as='span' color='gray.500' fontSize={'sm'}>
{course.lessons_count} lessons
</Text>
</Flex>
{course.lessons.length !== 0 ? (
<Stack spacing={'1'} mt='2' overflowY={'auto'} maxH={'445px'} pr='5px'>
{course.lessons.map(lesson => {
return (
<LinkBox
as='div'
display={'flex'}
key={lesson.id}
fontSize={'sm'}
justifyContent={'space-between'}
pl='5'
pr='5'
pb='5'
pt='5'
minH='85px'
bg={color}
borderRadius={'xl'}
transition={'ease-in-out .1s'}
_hover={{
bg: 'blue.500',
color: 'white',
}}
>
<Flex alignItems={'center'}>
<Icon as={MdPlayCircleOutline} w={5} h={5} mr='2' />
<LinkOverlay
as={ReactRouterLink}
fontWeight='medium'
to={`/lesson/${lesson.id}`}
overflowY={'hidden'}
maxH='60px'
>
{lesson.title}
</LinkOverlay>
</Flex>
<Text as='span'>{moment.duration(lesson.duration).asMinutes()}min</Text>
</LinkBox>
);
})}
</Stack>
) : (
<Box textAlign={'center'} p='15px 0'>
<Text fontSize={'sm'} userSelect={'none'} color='gray.500'>
There are no lessons here yet
</Text>
</Box>
)}
<Divider mt='5' />
</Box>
</GridItem>
</Grid>
</PageContent>
</PageWrapper>
);
};
export default CoursePage;

View File

@ -8,6 +8,7 @@ import AuthPage from '@educt/pages/auth/auth';
import NotFoundPage from '@educt/pages/404/404';
import CreateCoursePage from '@educt/pages/courses/create/create';
import CreateLessonPage from '@educt/pages/courses/create-lesson/create-lesson';
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';
@ -32,6 +33,13 @@ export const routes: IRouteOption[] = [
exact: true,
isPrivate: true,
},
{
path: '/course/:id',
title: 'Course',
component: CoursePage,
isPrivate: true,
exact: true,
},
{
path: '/courses',
title: 'Courses',

View File

@ -35,9 +35,7 @@ class CourseService {
* @param id Course id
* @returns Course
*/
public async fetchById(
id: string
): Promise<IApiRespose<Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>>> {
public async fetchById(id: string): Promise<IApiRespose<ICourse>> {
const result = await this.api.get(`/v1/courses/${id}`);
return result.data;
}

24
src/theme.ts Normal file
View File

@ -0,0 +1,24 @@
import { extendTheme } from '@chakra-ui/react';
export const theme = extendTheme({
styles: {
global: {
body: {
scrollbarWidth: 'thin',
scrollbarColor: '#000',
},
'::-webkit-scrollbar': {
width: '5px',
height: '5px',
},
'::-webkit-scrollbar-track': {
backgroundColor: 'gray.100',
borderRadius: '100px',
},
'::-webkit-scrollbar-thumb': {
background: 'blue.600',
borderRadius: '100px',
},
},
},
});