diff --git a/src/enums/index.ts b/src/enums/index.ts index e6791a8..c3fc012 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -3,3 +3,8 @@ export enum UserRoleEnum { TEACHER = 'teacher', STUDENT = 'student', } + +export enum CourseStatusEnum { + PUBLISHED = 'PUBLISHED', + DRAFT = 'DRAFT', +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 0dd927c..9885c4d 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,6 +1,6 @@ import type { RouteComponentProps } from 'react-router-dom'; import { LinkType } from 'types'; -import { UserRoleEnum } from '../enums'; +import { CourseStatusEnum, UserRoleEnum } from '../enums'; export interface IAppConfig { metaData: { @@ -20,6 +20,8 @@ export interface ICourse { id: string; title: string; description: string; + status: CourseStatusEnum; + image: null | {}; teacher: Pick; category: ICategory; lessons: ILesson[]; diff --git a/src/pages/courses/components/CategoryList/CategoryList.tsx b/src/pages/courses/components/CategoryList/CategoryList.tsx index 09b2ae9..6bb3735 100644 --- a/src/pages/courses/components/CategoryList/CategoryList.tsx +++ b/src/pages/courses/components/CategoryList/CategoryList.tsx @@ -4,11 +4,11 @@ import { CoursesPageContext } from 'contexts'; import CategoryItem from './CategoryItem'; import { ICategory } from 'interfaces'; -type CategoryListProps = { +type CategoryListPropsType = { categories: ICategory[]; }; -const CategoryList: React.FC = ({ categories }) => { +const CategoryList: React.FC = ({ categories }) => { const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext); /** diff --git a/src/pages/courses/components/CourseList/CourseItem.tsx b/src/pages/courses/components/CourseList/CourseItem.tsx index 992627f..f4226e8 100644 --- a/src/pages/courses/components/CourseList/CourseItem.tsx +++ b/src/pages/courses/components/CourseList/CourseItem.tsx @@ -13,16 +13,27 @@ import { Link, IconButton, } 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 { 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'; -const CourseItem: React.FC = () => { +type CourseItemPropsType = { + course: Omit; +}; + +const CourseItem: React.FC = ({ course }) => { const { userStore: { me }, } = useRootStore(); @@ -56,7 +67,7 @@ const CourseItem: React.FC = () => { )} { /> - ReactJS + {course.category.title} - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + {course.title} + {course.description} - 24 lesson + {course.lessons_count} lessons - 12 students + {course.students_count} students - 4 likes + {course.likes_count} likes diff --git a/src/pages/courses/components/CourseList/CourseList.tsx b/src/pages/courses/components/CourseList/CourseList.tsx index 9b87bbb..8fee619 100644 --- a/src/pages/courses/components/CourseList/CourseList.tsx +++ b/src/pages/courses/components/CourseList/CourseList.tsx @@ -1,12 +1,21 @@ import React from 'react'; import { Grid } from '@chakra-ui/react'; +/** + * Types + */ +import { ICourse } from 'interfaces'; + /** * Components */ import CourseItem from './CourseItem'; -const CourseList: React.FC = () => { +type CourseListPropsType = { + courses: Omit[]; +}; + +const CourseList: React.FC = ({ courses }) => { return ( { }, }} > - {[1, 2, 3, 4, 5, 6, 7, 8].map(i => { - return ; + {courses.map(course => { + return ; })} ); diff --git a/src/pages/courses/components/CourseList/CourseListLoading.tsx b/src/pages/courses/components/CourseList/CourseListLoading.tsx new file mode 100644 index 0000000..244f981 --- /dev/null +++ b/src/pages/courses/components/CourseList/CourseListLoading.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Grid, Skeleton } from '@chakra-ui/react'; + +const CourseListLoading: React.FC = () => { + return ( + + + + + + ); +}; + +export default CourseListLoading; diff --git a/src/pages/courses/index.tsx b/src/pages/courses/index.tsx index e001119..968f2f2 100644 --- a/src/pages/courses/index.tsx +++ b/src/pages/courses/index.tsx @@ -18,6 +18,7 @@ import { useRootStore } from 'hooks/useRootStore'; import { CoursesPageContextProvider } from 'providers'; import { useErrorHandler } from 'react-error-boundary'; import CategoryListLoading from './components/CategoryList/CategoryListLoading'; +import CourseListLoading from './components/CourseList/CourseListLoading'; /** * Courses page @@ -26,14 +27,16 @@ const CoursesPage: React.FC = ({ title }) => { const { userStore: { me }, categoryStore, + courseStore, } = useRootStore(); const handleError = useErrorHandler(); useEffect(() => { if (me !== null) { categoryStore.loadCategories().catch(error => handleError(error)); + courseStore.loadCourses().catch(error => handleError(error)); } - }, [me, categoryStore, handleError]); + }, [me, categoryStore, courseStore, handleError]); /** * TODO: Loading page for courses @@ -73,7 +76,7 @@ const CoursesPage: React.FC = ({ title }) => { )} {/* Course List Rendering */} - + {courseStore.courses !== null ? : } diff --git a/src/services/CourseService.ts b/src/services/CourseService.ts new file mode 100644 index 0000000..f4fde02 --- /dev/null +++ b/src/services/CourseService.ts @@ -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[]>> { + const result = await this.api.get('/v1/courses'); + return result.data; + } +} diff --git a/src/stores/CourseStore.ts b/src/stores/CourseStore.ts new file mode 100644 index 0000000..eff9daa --- /dev/null +++ b/src/stores/CourseStore.ts @@ -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[] | 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; + } + } +} diff --git a/src/stores/RootStore.ts b/src/stores/RootStore.ts index 26db41f..18d9261 100644 --- a/src/stores/RootStore.ts +++ b/src/stores/RootStore.ts @@ -13,8 +13,11 @@ import AuthStore from './AuthStore'; import UserStore from './UserStore'; import UIStore from './UIStore'; import CategoryStore from './CategoryStore'; +import CourseStore from './CourseStore'; export default class RootStore { + public courseStore: CourseStore; + public categoryStore: CategoryStore; public uiStore: UIStore; @@ -34,6 +37,7 @@ export default class RootStore { /** * Stores */ + this.courseStore = new CourseStore(this, this.apiService.api); this.categoryStore = new CategoryStore(this, this.apiService.api); this.uiStore = new UIStore(this); this.authStore = new AuthStore(this, this.apiService.api);