mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 02:09:08 +03:00
feat: added layout
This commit is contained in:
parent
f8ae2107eb
commit
63082e50c2
@ -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);
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -21,7 +21,7 @@ const NotFoundPage: React.FC<IPageProps> = ({ title }) => {
|
||||
|
||||
useEffect(() => {
|
||||
toast({ title: `Page not found`, duration: 10000, isClosable: true, status: 'warning' });
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
221
src/pages/course/course.tsx
Normal 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> •
|
||||
<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'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;
|
||||
@ -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',
|
||||
|
||||
@ -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
24
src/theme.ts
Normal 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',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user