feat: rendering courses on courses page

This commit is contained in:
Sergey Yarkov 2021-10-11 23:15:20 +03:00
parent b0d69599d7
commit c0e324890f
10 changed files with 124 additions and 21 deletions

View File

@ -3,3 +3,8 @@ export enum UserRoleEnum {
TEACHER = 'teacher', TEACHER = 'teacher',
STUDENT = 'student', STUDENT = 'student',
} }
export enum CourseStatusEnum {
PUBLISHED = 'PUBLISHED',
DRAFT = 'DRAFT',
}

View File

@ -1,6 +1,6 @@
import type { RouteComponentProps } from 'react-router-dom'; import type { RouteComponentProps } from 'react-router-dom';
import { LinkType } from 'types'; import { LinkType } from 'types';
import { UserRoleEnum } from '../enums'; import { CourseStatusEnum, UserRoleEnum } from '../enums';
export interface IAppConfig { export interface IAppConfig {
metaData: { metaData: {
@ -20,6 +20,8 @@ export interface ICourse {
id: string; id: string;
title: string; title: string;
description: string; description: string;
status: CourseStatusEnum;
image: null | {};
teacher: Pick<IUser, 'id' | 'first_name' | 'last_name' | 'email'>; teacher: Pick<IUser, 'id' | 'first_name' | 'last_name' | 'email'>;
category: ICategory; category: ICategory;
lessons: ILesson[]; lessons: ILesson[];

View File

@ -4,11 +4,11 @@ import { CoursesPageContext } from 'contexts';
import CategoryItem from './CategoryItem'; import CategoryItem from './CategoryItem';
import { ICategory } from 'interfaces'; import { ICategory } from 'interfaces';
type CategoryListProps = { type CategoryListPropsType = {
categories: ICategory[]; categories: ICategory[];
}; };
const CategoryList: React.FC<CategoryListProps> = ({ categories }) => { const CategoryList: React.FC<CategoryListPropsType> = ({ categories }) => {
const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext); const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext);
/** /**

View File

@ -13,16 +13,27 @@ import {
Link, Link,
IconButton, IconButton,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { Icon } from '@chakra-ui/react';
import { MdNote, MdMoreHoriz } from 'react-icons/md';
import { MdGroup, MdThumbUp, MdVideoLibrary } from 'react-icons/md';
import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
import { useColorMode } from '@chakra-ui/color-mode';
import { Link as ReactRouterLink } from 'react-router-dom'; import { Link as ReactRouterLink } from 'react-router-dom';
import { Icon } from '@chakra-ui/react';
import { MdNote, MdMoreHoriz, MdGroup, MdThumbUp, MdVideoLibrary } from 'react-icons/md';
import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
/**
* Types
*/
import { ICourse } from 'interfaces';
/**
* Hooks
*/
import { useColorMode } from '@chakra-ui/color-mode';
import { useRootStore } from 'hooks/useRootStore'; import { useRootStore } from 'hooks/useRootStore';
const CourseItem: React.FC = () => { type CourseItemPropsType = {
course: Omit<ICourse, 'teacher' | 'students' | 'lessons'>;
};
const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
const { const {
userStore: { me }, userStore: { me },
} = useRootStore(); } = useRootStore();
@ -56,7 +67,7 @@ const CourseItem: React.FC = () => {
</Flex> </Flex>
)} )}
<Link <Link
to='/course/id' to={`/course/${course.id}`}
as={ReactRouterLink} as={ReactRouterLink}
display='block' display='block'
textDecoration='none' textDecoration='none'
@ -86,22 +97,23 @@ const CourseItem: React.FC = () => {
/> />
</Box> </Box>
<Box p='0 10px' mt='10px' pb='20px'> <Box p='0 10px' mt='10px' pb='20px'>
<Badge colorScheme='blue'>ReactJS</Badge> <Badge colorScheme='blue'>{course.category.title}</Badge>
<Heading as='h2' fontSize='xl' mt='5px'> <Heading as='h2' fontSize='xl' mt='5px'>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. {course.title}
</Heading> </Heading>
<Text>{course.description}</Text>
<Flex mt='20px' flexWrap='wrap'> <Flex mt='20px' flexWrap='wrap'>
<Flex alignItems='center' mr='1'> <Flex alignItems='center' mr='1'>
<Icon as={MdVideoLibrary} mr='2' /> <Icon as={MdVideoLibrary} mr='2' />
<Text mr='4'>24 lesson</Text> <Text mr='4'>{course.lessons_count} lessons</Text>
</Flex> </Flex>
<Flex alignItems='center' mr='1'> <Flex alignItems='center' mr='1'>
<Icon as={MdGroup} mr='2' /> <Icon as={MdGroup} mr='2' />
<Text mr='4'>12 students</Text> <Text mr='4'>{course.students_count} students</Text>
</Flex> </Flex>
<Flex alignItems='center' mr='1'> <Flex alignItems='center' mr='1'>
<Icon as={MdThumbUp} mr='2' /> <Icon as={MdThumbUp} mr='2' />
<Text>4 likes</Text> <Text>{course.likes_count} likes</Text>
</Flex> </Flex>
</Flex> </Flex>
</Box> </Box>

View File

@ -1,12 +1,21 @@
import React from 'react'; import React from 'react';
import { Grid } from '@chakra-ui/react'; import { Grid } from '@chakra-ui/react';
/**
* Types
*/
import { ICourse } from 'interfaces';
/** /**
* Components * Components
*/ */
import CourseItem from './CourseItem'; import CourseItem from './CourseItem';
const CourseList: React.FC = () => { type CourseListPropsType = {
courses: Omit<ICourse, 'teacher' | 'students' | 'lessons'>[];
};
const CourseList: React.FC<CourseListPropsType> = ({ courses }) => {
return ( return (
<Grid <Grid
templateColumns='repeat(3, 1fr)' templateColumns='repeat(3, 1fr)'
@ -20,8 +29,8 @@ const CourseList: React.FC = () => {
}, },
}} }}
> >
{[1, 2, 3, 4, 5, 6, 7, 8].map(i => { {courses.map(course => {
return <CourseItem key={i} />; return <CourseItem key={course.id} course={course} />;
})} })}
</Grid> </Grid>
); );

View File

@ -0,0 +1,14 @@
import React from 'react';
import { Grid, Skeleton } from '@chakra-ui/react';
const CourseListLoading: React.FC = () => {
return (
<Grid templateColumns='repeat(auto-fit, minmax(300px, 1fr))' gap='6'>
<Skeleton height='200px' borderRadius='lg' />
<Skeleton height='200px' borderRadius='lg' />
<Skeleton height='200px' borderRadius='lg' />
</Grid>
);
};
export default CourseListLoading;

View File

@ -18,6 +18,7 @@ import { useRootStore } from 'hooks/useRootStore';
import { CoursesPageContextProvider } from 'providers'; import { CoursesPageContextProvider } from 'providers';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import CategoryListLoading from './components/CategoryList/CategoryListLoading'; import CategoryListLoading from './components/CategoryList/CategoryListLoading';
import CourseListLoading from './components/CourseList/CourseListLoading';
/** /**
* Courses page * Courses page
@ -26,14 +27,16 @@ const CoursesPage: React.FC<IPageProps> = ({ title }) => {
const { const {
userStore: { me }, userStore: { me },
categoryStore, categoryStore,
courseStore,
} = useRootStore(); } = useRootStore();
const handleError = useErrorHandler(); const handleError = useErrorHandler();
useEffect(() => { useEffect(() => {
if (me !== null) { if (me !== null) {
categoryStore.loadCategories().catch(error => handleError(error)); categoryStore.loadCategories().catch(error => handleError(error));
courseStore.loadCourses().catch(error => handleError(error));
} }
}, [me, categoryStore, handleError]); }, [me, categoryStore, courseStore, handleError]);
/** /**
* TODO: Loading page for courses * TODO: Loading page for courses
@ -73,7 +76,7 @@ const CoursesPage: React.FC<IPageProps> = ({ title }) => {
)} )}
{/* Course List Rendering */} {/* Course List Rendering */}
<CourseList /> {courseStore.courses !== null ? <CourseList courses={courseStore.courses} /> : <CourseListLoading />}
</Box> </Box>
</Box> </Box>
</Box> </Box>

View File

@ -0,0 +1,20 @@
import { AxiosInstance } from 'axios';
import { IApiRespose, ICourse } from 'interfaces';
export class CourseService {
public api: AxiosInstance;
constructor(api: AxiosInstance) {
this.api = api;
}
/**
* Fetch all courses
*
* @returns Array of courses
*/
public async fetchAll(): Promise<IApiRespose<Omit<ICourse, 'teacher' | 'students' | 'lessons'>[]>> {
const result = await this.api.get('/v1/courses');
return result.data;
}
}

34
src/stores/CourseStore.ts Normal file
View File

@ -0,0 +1,34 @@
import { AxiosInstance } from 'axios';
import { ICourse } from 'interfaces';
import { makeAutoObservable, runInAction } from 'mobx';
import { CourseService } from 'services/CourseService';
import RootStore from './RootStore';
export default class CourseStore {
public root: RootStore;
public courseService: CourseService;
public courses: Omit<ICourse, 'teacher' | 'students' | 'lessons'>[] | null = null;
constructor(root: RootStore, api: AxiosInstance) {
this.root = root;
this.courseService = new CourseService(api);
makeAutoObservable(this);
}
public async loadCourses() {
try {
const result = await this.courseService.fetchAll();
console.log(`[${this.constructor.name}]: ${result.message}`, result);
runInAction(() => {
this.courses = result.data;
});
return result;
} catch (error: any) {
throw error;
}
}
}

View File

@ -13,8 +13,11 @@ import AuthStore from './AuthStore';
import UserStore from './UserStore'; import UserStore from './UserStore';
import UIStore from './UIStore'; import UIStore from './UIStore';
import CategoryStore from './CategoryStore'; import CategoryStore from './CategoryStore';
import CourseStore from './CourseStore';
export default class RootStore { export default class RootStore {
public courseStore: CourseStore;
public categoryStore: CategoryStore; public categoryStore: CategoryStore;
public uiStore: UIStore; public uiStore: UIStore;
@ -34,6 +37,7 @@ export default class RootStore {
/** /**
* Stores * Stores
*/ */
this.courseStore = new CourseStore(this, this.apiService.api);
this.categoryStore = new CategoryStore(this, this.apiService.api); this.categoryStore = new CategoryStore(this, this.apiService.api);
this.uiStore = new UIStore(this); this.uiStore = new UIStore(this);
this.authStore = new AuthStore(this, this.apiService.api); this.authStore = new AuthStore(this, this.apiService.api);