mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: edit lesson page
This commit is contained in:
parent
76f72dc9c4
commit
0dc80fd0e8
@ -1,21 +1,43 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import * as helpres from '@educt/helpers';
|
import * as helpers from '@educt/helpers';
|
||||||
import { Stack, Flex, Box, Icon, Text, Input, IconButton, Button } from '@chakra-ui/react';
|
import { Stack, Flex, Box, Icon, Text, Input, IconButton, Button } from '@chakra-ui/react';
|
||||||
import { MdOutlineFilePresent } from 'react-icons/md';
|
import { MdAttachFile, MdOutlineFilePresent } from 'react-icons/md';
|
||||||
import { AddIcon, CloseIcon } from '@chakra-ui/icons';
|
import { CloseIcon } from '@chakra-ui/icons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { FileSupportedFormatsEnum } from '@educt/enums';
|
import { FileSupportedFormatsEnum } from '@educt/enums';
|
||||||
|
import { AttachmentFileType } from '@educt/types';
|
||||||
|
|
||||||
type FilesUploaderPropsType = {
|
type FilesUploaderPropsType = {
|
||||||
onChange: (files: File[]) => void;
|
onChange: (files: File[]) => void;
|
||||||
files?: File[] | undefined;
|
files?: File[] | undefined;
|
||||||
|
preloadedFiles?: AttachmentFileType[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FilesUploader: React.FC<FilesUploaderPropsType> = ({ onChange, files }) => {
|
const FileContent: React.FC<{ name: string; size: number }> = ({ name, size }) => (
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Icon as={MdOutlineFilePresent} mr='2' h={4} w={4} />
|
||||||
|
<Text overflow={'hidden'} fontSize={'sm'} fontWeight={'medium'} lineHeight={'1rem'}>
|
||||||
|
{name}
|
||||||
|
<br />
|
||||||
|
<Text as='small' color='gray.500'>
|
||||||
|
{helpers.transformBytes(size)}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FileWrapper: React.FC = ({ children }) => (
|
||||||
|
<Flex alignItems={'center'} justifyContent={'space-between'} p='3' bg={'gray.50'} borderRadius={'lg'}>
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FilesUploader: React.FC<FilesUploaderPropsType> = ({ onChange, files, preloadedFiles }) => {
|
||||||
|
const [preloaded, setPreloaded] = useState<AttachmentFileType[]>(preloadedFiles || []);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const onSelectFileHandler = () => {
|
const onSelectFileHandler = () => {
|
||||||
@ -59,27 +81,30 @@ const FilesUploader: React.FC<FilesUploaderPropsType> = ({ onChange, files }) =>
|
|||||||
.map(ext => `.${ext.toLowerCase()}`)
|
.map(ext => `.${ext.toLowerCase()}`)
|
||||||
.join(', ')}
|
.join(', ')}
|
||||||
/>
|
/>
|
||||||
|
{preloadedFiles && files?.length === 0 && (
|
||||||
|
<Stack>
|
||||||
|
{preloaded.map((file, index) => (
|
||||||
|
<FileWrapper key={index}>
|
||||||
|
<FileContent name={file.name} size={file.size} />
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setPreloaded(prev => prev.filter(file => file.size !== file.size));
|
||||||
|
onChange([]);
|
||||||
|
}}
|
||||||
|
aria-label='remove'
|
||||||
|
variant={'unstyled'}
|
||||||
|
size='xs'
|
||||||
|
icon={<CloseIcon />}
|
||||||
|
/>
|
||||||
|
</FileWrapper>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
{files && (
|
{files && (
|
||||||
<Stack>
|
<Stack>
|
||||||
{files.map((file, index) => (
|
{files.map((file, index) => (
|
||||||
<Flex
|
<FileWrapper key={index}>
|
||||||
key={index}
|
<FileContent name={file.name} size={file.size} />
|
||||||
alignItems={'center'}
|
|
||||||
justifyContent={'space-between'}
|
|
||||||
p='3'
|
|
||||||
bg={'gray.50'}
|
|
||||||
borderRadius={'lg'}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Icon as={MdOutlineFilePresent} mr='2' h={4} w={4} />
|
|
||||||
<Text overflow={'hidden'} fontSize={'sm'} fontWeight={'medium'} lineHeight={'1rem'}>
|
|
||||||
{file.name}
|
|
||||||
<br />
|
|
||||||
<Text as='small' color='gray.500'>
|
|
||||||
{helpres.transformBytes(file.size)}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={onRemoveSelectedFileHandler(file)}
|
onClick={onRemoveSelectedFileHandler(file)}
|
||||||
aria-label='remove'
|
aria-label='remove'
|
||||||
@ -87,12 +112,24 @@ const FilesUploader: React.FC<FilesUploaderPropsType> = ({ onChange, files }) =>
|
|||||||
size='xs'
|
size='xs'
|
||||||
icon={<CloseIcon />}
|
icon={<CloseIcon />}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</FileWrapper>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
<Button onClick={onSelectFileHandler} float={'right'} leftIcon={<AddIcon />} variant={'outline'} size='sm' mt='3'>
|
{files?.length === 0 && preloaded?.length === 0 && (
|
||||||
Upload new
|
<Text color={'gray.500'} userSelect={'none'} fontSize={'sm'} textAlign={'center'}>
|
||||||
|
No file selected
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={onSelectFileHandler}
|
||||||
|
float={'right'}
|
||||||
|
leftIcon={<MdAttachFile />}
|
||||||
|
variant={'outline'}
|
||||||
|
size='sm'
|
||||||
|
mt='3'
|
||||||
|
>
|
||||||
|
Select files
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { LessonForm } from '.';
|
|||||||
/**
|
/**
|
||||||
* Validator
|
* Validator
|
||||||
*/
|
*/
|
||||||
import LessonFormSchema from './LessonForm.validator';
|
import { CreateLessonFormSchema } from './LessonForm.validator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
@ -16,7 +16,7 @@ import { useCreateLesson } from '@educt/hooks/queries';
|
|||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
const CreateLessonForm: React.FC = () => {
|
const CreateLessonForm: React.FC = () => {
|
||||||
const form = useForm<InputFields>({ resolver: yupResolver(LessonFormSchema) });
|
const form = useForm<InputFields>({ resolver: yupResolver(CreateLessonFormSchema) });
|
||||||
const { createLesson, isLoading } = useCreateLesson();
|
const { createLesson, isLoading } = useCreateLesson();
|
||||||
const { id: course_id } = useParams<{ id: string }>();
|
const { id: course_id } = useParams<{ id: string }>();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|||||||
62
src/components/Forms/LessonForm/EditLessonForm.tsx
Normal file
62
src/components/Forms/LessonForm/EditLessonForm.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as helpers from '@educt/helpers';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
|
import type { InputFields } from './LessonForm';
|
||||||
|
import { LessonForm } from '.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validator
|
||||||
|
*/
|
||||||
|
import { EditLessonFormSchema } from './LessonForm.validator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
|
import { ILesson } from '@educt/interfaces';
|
||||||
|
|
||||||
|
type EditLessonFormPropsType = {
|
||||||
|
lesson: ILesson;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditLessonForm: React.FC<EditLessonFormPropsType> = ({ lesson }) => {
|
||||||
|
const form = useForm<InputFields>({
|
||||||
|
resolver: yupResolver(EditLessonFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
title: lesson.title,
|
||||||
|
description: lesson.description,
|
||||||
|
duration: lesson.duration,
|
||||||
|
video: undefined,
|
||||||
|
materials: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { id: lesson_id } = useParams<{ id: string }>();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<InputFields> = async data => {
|
||||||
|
const updatedFields = helpers.getDirtyFields(form.formState.dirtyFields, data);
|
||||||
|
console.log(updatedFields);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LessonForm
|
||||||
|
isLoading={false}
|
||||||
|
reactHookForm={{ ...form }}
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
buttonLabel='Save'
|
||||||
|
preloadedVideo={lesson.video || undefined}
|
||||||
|
preloadedMaterials={lesson.materials.map(material => {
|
||||||
|
return {
|
||||||
|
name: material.client_name,
|
||||||
|
extname: material.ext,
|
||||||
|
size: material.size,
|
||||||
|
url: `materials/${material.name}`,
|
||||||
|
mimeType: '',
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditLessonForm;
|
||||||
@ -17,6 +17,7 @@ import { Controller, UseFormReturn } from 'react-hook-form';
|
|||||||
import VideoUploader from '@educt/components/VideoUploader';
|
import VideoUploader from '@educt/components/VideoUploader';
|
||||||
import FilesUploader from '@educt/components/FilesUploader';
|
import FilesUploader from '@educt/components/FilesUploader';
|
||||||
import { FileSupportedFormatsEnum, VideoSupportedFormatsEnum } from '@educt/enums';
|
import { FileSupportedFormatsEnum, VideoSupportedFormatsEnum } from '@educt/enums';
|
||||||
|
import type { AttachmentFileType, LessonVideoType } from '@educt/types';
|
||||||
|
|
||||||
export type InputFields = {
|
export type InputFields = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -31,9 +32,18 @@ export type LessonFormPropsType = {
|
|||||||
buttonLabel: string;
|
buttonLabel: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
reactHookForm: UseFormReturn<InputFields>;
|
reactHookForm: UseFormReturn<InputFields>;
|
||||||
|
preloadedVideo?: LessonVideoType | undefined;
|
||||||
|
preloadedMaterials?: AttachmentFileType[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LessonForm: React.FC<LessonFormPropsType> = ({ onSubmit, buttonLabel, isLoading, reactHookForm }) => {
|
const LessonForm: React.FC<LessonFormPropsType> = ({
|
||||||
|
onSubmit,
|
||||||
|
buttonLabel,
|
||||||
|
isLoading,
|
||||||
|
reactHookForm,
|
||||||
|
preloadedVideo,
|
||||||
|
preloadedMaterials,
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
register,
|
register,
|
||||||
@ -70,7 +80,7 @@ const LessonForm: React.FC<LessonFormPropsType> = ({ onSubmit, buttonLabel, isLo
|
|||||||
control={control}
|
control={control}
|
||||||
name='video'
|
name='video'
|
||||||
render={({ field: { onChange, value: file } }) => (
|
render={({ field: { onChange, value: file } }) => (
|
||||||
<VideoUploader onChange={file => onChange(file)} file={file} />
|
<VideoUploader onChange={file => onChange(file)} file={file} preloadedVideoUrl={preloadedVideo?.url} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Text as='small' color='red.500'>
|
<Text as='small' color='red.500'>
|
||||||
@ -99,7 +109,11 @@ const LessonForm: React.FC<LessonFormPropsType> = ({ onSubmit, buttonLabel, isLo
|
|||||||
control={control}
|
control={control}
|
||||||
name='materials'
|
name='materials'
|
||||||
render={({ field: { onChange, value: files } }) => (
|
render={({ field: { onChange, value: files } }) => (
|
||||||
<FilesUploader onChange={files => onChange(files)} files={Array.from(files || [])} />
|
<FilesUploader
|
||||||
|
onChange={files => onChange(files)}
|
||||||
|
files={Array.from(files || [])}
|
||||||
|
preloadedFiles={preloadedMaterials}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Text as='small' color='red.500'>
|
<Text as='small' color='red.500'>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import yup from '@educt/schema';
|
|||||||
const VIDEO_FILE_SIZE = 5000 * 1024 * 1024;
|
const VIDEO_FILE_SIZE = 5000 * 1024 * 1024;
|
||||||
const MATERIAL_FILE_SIZE = 100000 * 1024;
|
const MATERIAL_FILE_SIZE = 100000 * 1024;
|
||||||
|
|
||||||
const LessonFormSchema = yup
|
const CreateLessonFormSchema = yup
|
||||||
.object({
|
.object({
|
||||||
title: yup.string().required('Title field name is required').max(90, 'Title must be at most 90 characters'),
|
title: yup.string().required('Title field name is required').max(90, 'Title must be at most 90 characters'),
|
||||||
description: yup
|
description: yup
|
||||||
@ -13,28 +13,29 @@ const LessonFormSchema = yup
|
|||||||
.max(250, 'Description must be at most 250 characters'),
|
.max(250, 'Description must be at most 250 characters'),
|
||||||
duration: yup.string().required('Duration field name is required'),
|
duration: yup.string().required('Duration field name is required'),
|
||||||
video: yup
|
video: yup
|
||||||
.mixed()
|
.mixed<File>()
|
||||||
.test(
|
.test('fileSize', `File size is too large, maximum size is ${helpers.transformBytes(VIDEO_FILE_SIZE)}`, value =>
|
||||||
'fileSize',
|
!value ? true : value.size <= VIDEO_FILE_SIZE
|
||||||
`File size is too large, maximum size is ${helpers.transformBytes(VIDEO_FILE_SIZE)}`,
|
|
||||||
(value: File) => {
|
|
||||||
if (!value) return true;
|
|
||||||
return value && value.size <= VIDEO_FILE_SIZE;
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.required('Video field name is required'),
|
.required('Video field name is required'),
|
||||||
materials: yup
|
materials: yup
|
||||||
.mixed()
|
.mixed<File[]>()
|
||||||
.test(
|
.test(
|
||||||
'fileSize',
|
'fileSize',
|
||||||
`File size is too large, maximum size is ${helpers.transformBytes(MATERIAL_FILE_SIZE)}`,
|
`File size is too large, maximum size is ${helpers.transformBytes(MATERIAL_FILE_SIZE)}`,
|
||||||
(values: File[]) => {
|
values => (!values ? true : values.every(value => value.size <= MATERIAL_FILE_SIZE))
|
||||||
if (!values) return true;
|
|
||||||
return values && values.every(value => value.size <= MATERIAL_FILE_SIZE);
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export default LessonFormSchema;
|
const EditLessonFormSchema = CreateLessonFormSchema.shape({
|
||||||
|
video: yup
|
||||||
|
.mixed<File>()
|
||||||
|
.test('fileSize', `File size is too large, maximum size is ${helpers.transformBytes(VIDEO_FILE_SIZE)}`, value =>
|
||||||
|
!value ? true : value.size <= VIDEO_FILE_SIZE
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export { CreateLessonFormSchema, EditLessonFormSchema };
|
||||||
|
|||||||
@ -2,16 +2,19 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||||||
import { Flex, Box, Icon, Text, Button, Input, AspectRatio, useColorModeValue } from '@chakra-ui/react';
|
import { Flex, Box, Icon, Text, Button, Input, AspectRatio, useColorModeValue } from '@chakra-ui/react';
|
||||||
import { MdUpload } from 'react-icons/md';
|
import { MdUpload } from 'react-icons/md';
|
||||||
import { VideoSupportedFormatsEnum } from '@educt/enums';
|
import { VideoSupportedFormatsEnum } from '@educt/enums';
|
||||||
|
import { LessonVideoType } from '@educt/types';
|
||||||
|
|
||||||
type VideoUploaderPropsType = {
|
type VideoUploaderPropsType = {
|
||||||
onChange: (file: File) => void;
|
onChange: (file: File) => void;
|
||||||
file?: File | undefined;
|
file?: File | LessonVideoType | undefined;
|
||||||
|
preloadedVideoUrl?: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const VideoUploader: React.FC<VideoUploaderPropsType> = ({ onChange, file }) => {
|
const VideoUploader: React.FC<VideoUploaderPropsType> = ({ onChange, file, preloadedVideoUrl }) => {
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const [videoObjectUrl, setVideoObjectUrl] = useState<string | undefined>(undefined);
|
const [videoObjectUrl, setVideoObjectUrl] = useState<string | undefined>(undefined);
|
||||||
|
const isPreloadedVideo = preloadedVideoUrl !== undefined;
|
||||||
|
|
||||||
const onSelectFileHandler = () => {
|
const onSelectFileHandler = () => {
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
@ -38,12 +41,24 @@ const VideoUploader: React.FC<VideoUploaderPropsType> = ({ onChange, file }) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ChangeVideoButton = () => (
|
||||||
|
<Button onClick={onSelectFileHandler} leftIcon={<MdUpload />} variant={'outline'} size='sm' mt='2' float='right'>
|
||||||
|
Change video
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
videoRef.current.load();
|
videoRef.current.load();
|
||||||
}
|
}
|
||||||
}, [videoObjectUrl]);
|
}, [videoObjectUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (file && file instanceof File) {
|
||||||
|
setVideoObjectUrl(URL.createObjectURL(file));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{file && videoObjectUrl ? (
|
{file && videoObjectUrl ? (
|
||||||
@ -53,18 +68,9 @@ const VideoUploader: React.FC<VideoUploaderPropsType> = ({ onChange, file }) =>
|
|||||||
<source src={videoObjectUrl} />
|
<source src={videoObjectUrl} />
|
||||||
</video>
|
</video>
|
||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
<Button
|
<ChangeVideoButton />
|
||||||
onClick={onSelectFileHandler}
|
|
||||||
leftIcon={<MdUpload />}
|
|
||||||
variant={'outline'}
|
|
||||||
size='sm'
|
|
||||||
mt='2'
|
|
||||||
float='right'
|
|
||||||
>
|
|
||||||
Change video
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : !isPreloadedVideo ? (
|
||||||
<Flex
|
<Flex
|
||||||
p='40px 10px'
|
p='40px 10px'
|
||||||
textAlign={'center'}
|
textAlign={'center'}
|
||||||
@ -89,6 +95,15 @@ const VideoUploader: React.FC<VideoUploaderPropsType> = ({ onChange, file }) =>
|
|||||||
Select file
|
Select file
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<AspectRatio maxH='lg' mt='2'>
|
||||||
|
<video controls style={{ borderRadius: '5px' }}>
|
||||||
|
<source src={preloadedVideoUrl} />
|
||||||
|
</video>
|
||||||
|
</AspectRatio>
|
||||||
|
<ChangeVideoButton />
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Input
|
<Input
|
||||||
type='file'
|
type='file'
|
||||||
@ -97,6 +112,7 @@ const VideoUploader: React.FC<VideoUploaderPropsType> = ({ onChange, file }) =>
|
|||||||
maxW={'0'}
|
maxW={'0'}
|
||||||
h='0'
|
h='0'
|
||||||
display={'block'}
|
display={'block'}
|
||||||
|
required={false}
|
||||||
opacity={'0'}
|
opacity={'0'}
|
||||||
ml='auto'
|
ml='auto'
|
||||||
mr='auto'
|
mr='auto'
|
||||||
|
|||||||
@ -80,6 +80,11 @@ export function transformBytes(bytes: number, decimals = 2): string {
|
|||||||
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transformBlobToFile(blob: Blob, fileName: string) {
|
||||||
|
const file = new File([blob], fileName, { type: blob.type });
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param array Array
|
* @param array Array
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { CourseStatusEnum } from '@educt/enums';
|
|||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
|
|
||||||
export const CoursesTabs: React.FC<BoxProps> = props => {
|
export const CoursesTabs: React.FC<BoxProps> = props => {
|
||||||
@ -14,6 +15,10 @@ export const CoursesTabs: React.FC<BoxProps> = props => {
|
|||||||
pageStore: { coursesStore },
|
pageStore: { coursesStore },
|
||||||
} = useRootStore();
|
} = useRootStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
coursesStore.setShowedStatus(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
me &&
|
me &&
|
||||||
(me.isAdmin || me.isTeacher ? (
|
(me.isAdmin || me.isTeacher ? (
|
||||||
|
|||||||
@ -156,8 +156,9 @@ const LessonPage: React.FC<IPageProps> = () => {
|
|||||||
<Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }}>
|
<Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }}>
|
||||||
<GridItem>
|
<GridItem>
|
||||||
<Box>
|
<Box>
|
||||||
|
{lesson.video ? (
|
||||||
<ReactPlayer width={'100%'} height={'100%'} controls url={lesson.video?.url} />
|
<ReactPlayer width={'100%'} height={'100%'} controls url={lesson.video?.url} />
|
||||||
{!lesson.video && (
|
) : (
|
||||||
<Flex h='500px' justifyContent={'center'} alignItems={'center'}>
|
<Flex h='500px' justifyContent={'center'} alignItems={'center'}>
|
||||||
<Text color='gray.500' userSelect={'none'}>
|
<Text color='gray.500' userSelect={'none'}>
|
||||||
Video has not been uploaded yet.
|
Video has not been uploaded yet.
|
||||||
|
|||||||
32
src/pages/lessons/edit-lesson/edit-lesson.tsx
Normal file
32
src/pages/lessons/edit-lesson/edit-lesson.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IPageProps } from '@educt/interfaces';
|
||||||
|
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
|
||||||
|
import { Redirect, useParams } from 'react-router-dom';
|
||||||
|
import LoadingPage from '@educt/components/LoadingPage';
|
||||||
|
import { useFetchLesson } from '@educt/hooks/queries/lesson/useFetchLesson';
|
||||||
|
import EditLessonForm from '@educt/components/Forms/LessonForm/EditLessonForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Lesson page
|
||||||
|
*/
|
||||||
|
const EditLessonPage: React.FC<IPageProps> = () => {
|
||||||
|
const params = useParams<{ id: string }>();
|
||||||
|
const { error, data: lesson, isLoading } = useFetchLesson(params.id);
|
||||||
|
|
||||||
|
if (error?.response?.status === 404) {
|
||||||
|
return <Redirect to='/404' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lesson || isLoading) return <LoadingPage />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper>
|
||||||
|
<PageHeading heading='Lesson Editor' description={lesson.description} />
|
||||||
|
<PageContent>
|
||||||
|
<EditLessonForm lesson={lesson} />
|
||||||
|
</PageContent>
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditLessonPage;
|
||||||
@ -8,6 +8,7 @@ import AuthPage from '@educt/pages/auth/auth';
|
|||||||
import NotFoundPage from '@educt/pages/404/404';
|
import NotFoundPage from '@educt/pages/404/404';
|
||||||
import CreateCoursePage from '@educt/pages/courses/create/create';
|
import CreateCoursePage from '@educt/pages/courses/create/create';
|
||||||
import CreateLessonPage from '@educt/pages/courses/create-lesson/create-lesson';
|
import CreateLessonPage from '@educt/pages/courses/create-lesson/create-lesson';
|
||||||
|
import EditLessonPage from './pages/lessons/edit-lesson/edit-lesson';
|
||||||
import CoursePage from '@educt/pages/course/course';
|
import CoursePage from '@educt/pages/course/course';
|
||||||
import EditCoursePage from '@educt/pages/courses/edit/edit';
|
import EditCoursePage from '@educt/pages/courses/edit/edit';
|
||||||
import ChangePasswordPage from '@educt/pages/profile/change-password/change-password';
|
import ChangePasswordPage from '@educt/pages/profile/change-password/change-password';
|
||||||
@ -78,6 +79,14 @@ export const routes: IRouteOption[] = [
|
|||||||
exact: true,
|
exact: true,
|
||||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
|
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/lessons/edit/:id',
|
||||||
|
title: 'Edit Lesson',
|
||||||
|
component: EditLessonPage,
|
||||||
|
isPrivate: true,
|
||||||
|
exact: true,
|
||||||
|
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/lesson/:id',
|
path: '/lesson/:id',
|
||||||
title: 'Lesson',
|
title: 'Lesson',
|
||||||
|
|||||||
@ -88,6 +88,7 @@ class CourseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Create new course
|
||||||
*
|
*
|
||||||
* @param data Data for creating course
|
* @param data Data for creating course
|
||||||
* @returns
|
* @returns
|
||||||
@ -104,6 +105,13 @@ class CourseService {
|
|||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update course
|
||||||
|
*
|
||||||
|
* @param id Course id
|
||||||
|
* @param data Data to update
|
||||||
|
* @returns Updated course
|
||||||
|
*/
|
||||||
public async update(
|
public async update(
|
||||||
id: string,
|
id: string,
|
||||||
data: UpdateCourseParamsType
|
data: UpdateCourseParamsType
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user