mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 18:29:15 +03:00
feat: rendering courses on courses page
This commit is contained in:
parent
b0d69599d7
commit
c0e324890f
@ -3,3 +3,8 @@ export enum UserRoleEnum {
|
|||||||
TEACHER = 'teacher',
|
TEACHER = 'teacher',
|
||||||
STUDENT = 'student',
|
STUDENT = 'student',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CourseStatusEnum {
|
||||||
|
PUBLISHED = 'PUBLISHED',
|
||||||
|
DRAFT = 'DRAFT',
|
||||||
|
}
|
||||||
|
|||||||
@ -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[];
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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>
|
||||||
|
|||||||
20
src/services/CourseService.ts
Normal file
20
src/services/CourseService.ts
Normal 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
34
src/stores/CourseStore.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user