diff --git a/src/components/FilesUploader/FilesUploader.tsx b/src/components/FilesUploader/FilesUploader.tsx new file mode 100644 index 0000000..2cc4ed2 --- /dev/null +++ b/src/components/FilesUploader/FilesUploader.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import * as helpres from '@educt/helpers'; +import { Stack, Flex, Box, Icon, Text, Input, IconButton, Button } from '@chakra-ui/react'; +import { MdOutlineFilePresent } from 'react-icons/md'; +import { AddIcon, CloseIcon } from '@chakra-ui/icons'; + +/** + * Hooks + */ +import { useRef } from 'react'; +import { FileSupportedFormatsEnum } from '@educt/enums'; + +type FilesUploaderPropsType = { + onChange: (files: File[]) => void; + files?: Array | undefined; +}; + +const FilesUploader: React.FC = ({ onChange, files }) => { + const fileInputRef = useRef(null); + const [selectedFiles, setSelectedFiles] = useState>([]); + + const onSelectFileHandler = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + /** + * Detach file + */ + const onRemoveSelectedFileHandler = (selectedFile: File) => () => + setSelectedFiles(selectedFiles => { + const updated = selectedFiles.filter(file => file.name !== selectedFile.name); + onChange(updated); + return updated; + }); + + /** + * Attach file + */ + const onChangeFileHandler = (e: React.ChangeEvent) => { + e.preventDefault(); + + const { files } = e.target; + + if (files && files.length !== 0) { + const isAlreadySelected = selectedFiles.find(selectedFile => + Array.from(files).some(file => file.name === selectedFile.name) + ); + + if (isAlreadySelected) return; + + setSelectedFiles(selectedFiles => { + const updated = [...selectedFiles, ...Array.from(files)]; + onChange(updated); + return updated; + }); + } + }; + + return ( + + `.${ext.toLowerCase()}`) + .join(', ')} + /> + {files && ( + + {selectedFiles.map((file, index) => ( + + + + + {file.name} +
+ + {helpres.transformBytes(file.size)} + +
+
+ } + /> +
+ ))} +
+ )} + +
+ ); +}; + +export default FilesUploader; diff --git a/src/components/FilesUploader/index.ts b/src/components/FilesUploader/index.ts new file mode 100644 index 0000000..9ba1ae9 --- /dev/null +++ b/src/components/FilesUploader/index.ts @@ -0,0 +1,3 @@ +import FilesUploader from './FilesUploader'; + +export default FilesUploader; diff --git a/src/components/Forms/LessonForm/CreateLessonForm.tsx b/src/components/Forms/LessonForm/CreateLessonForm.tsx index 3165be4..4b9a8bb 100644 --- a/src/components/Forms/LessonForm/CreateLessonForm.tsx +++ b/src/components/Forms/LessonForm/CreateLessonForm.tsx @@ -10,6 +10,7 @@ const CreateLessonForm: React.FC = () => { const onSubmit: SubmitHandler = async data => { console.log('creating...'); + console.log(data); }; return ( diff --git a/src/components/Forms/LessonForm/LessonForm.tsx b/src/components/Forms/LessonForm/LessonForm.tsx index d31d406..0d758f3 100644 --- a/src/components/Forms/LessonForm/LessonForm.tsx +++ b/src/components/Forms/LessonForm/LessonForm.tsx @@ -11,13 +11,12 @@ import { Button, Textarea, Flex, - Icon, - IconButton, Divider, } from '@chakra-ui/react'; -import { AddIcon, CloseIcon } from '@chakra-ui/icons'; -import { UseFormReturn } from 'react-hook-form'; -import { MdOutlineFilePresent, MdUpload } from 'react-icons/md'; +import { Controller, UseFormReturn } from 'react-hook-form'; +import VideoUploader from '@educt/components/VideoUploader'; +import FilesUploader from '@educt/components/FilesUploader'; +import { FileSupportedFormatsEnum, VideoSupportedFormatsEnum } from '@educt/enums'; export type InputFields = { title: string; @@ -68,68 +67,51 @@ const LessonForm: React.FC = ({ onSubmit, buttonLabel, isLo Video - - - - - - - Upload a video file
- - The video will be uploaded to cloud or local storage depending on the server configuration. - -
-
- -
- Supported formats: mp4, mov, avi, wmv, webm, flv + ( + onChange(file)} file={file} /> + )} + /> + + {errors.video?.message} + + + Supported formats:{' '} + {Object.keys(VideoSupportedFormatsEnum) + .map(ext => `${ext.toLowerCase()}`) + .join(', ')} +
Video duration + + {errors.duration?.message} + hours:minutes:seconds Materials - - {[1, 2, 3].map(k => ( - - - - - file.pdf -
- - 245kB - -
-
- } /> -
- ))} -
- - Supported formats: pdf, zip, rar, doc, docx + ( + onChange(files)} files={files} /> + )} + /> + + {errors.materials?.map(m => m.message)} + + + Supported formats:{' '} + {Object.keys(FileSupportedFormatsEnum) + .map(ext => `${ext.toLowerCase()}`) + .join(', ')} +
diff --git a/src/components/Forms/LessonForm/LessonForm.validator.ts b/src/components/Forms/LessonForm/LessonForm.validator.ts index 0437da9..210d574 100644 --- a/src/components/Forms/LessonForm/LessonForm.validator.ts +++ b/src/components/Forms/LessonForm/LessonForm.validator.ts @@ -1,11 +1,37 @@ +import { VideoSupportedFormatsEnum } from '@educt/enums'; import yup from '@educt/schema'; +const VIDEO_FILE_SIZE = 5000 * 1024 * 1024; +const MATERIAL_FILE_SIZE = 10000 * 1024; + const LessonFormSchema = yup.object().shape({ title: yup.string().required('Title field name is required').max(90, 'Title must be at most 90 characters'), description: yup .string() .required('Description field name is required') .max(250, 'Description must be at most 250 characters'), + duration: yup.string().required('Duration field name is required'), + video: yup + .mixed() + .test('fileSize', 'File size is too large', (value: File) => { + if (!value) return true; + return value && value.size <= VIDEO_FILE_SIZE; + }) + .test('fileFormat', 'This file format is not supported', (value: File) => { + if (!value) return true; + return value && Object.values(VideoSupportedFormatsEnum).includes(value.type as VideoSupportedFormatsEnum); + }) + .required('Video field name is required'), + // materials: yup + // .mixed() + // .test('fileSize', 'File size is too large', (value: File) => { + // if (!value) return true; + // return value && value.size <= VIDEO_FILE_SIZE; + // }) + // .test('fileFormat', 'This file format is not supported', (value: File) => { + // if (!value) return true; + // return value && VIDEO_SUPPORTED_FORMATS.includes(value.type); + // }), }); export default LessonFormSchema; diff --git a/src/components/VideoUploader/VideoUploader.tsx b/src/components/VideoUploader/VideoUploader.tsx new file mode 100644 index 0000000..92b3269 --- /dev/null +++ b/src/components/VideoUploader/VideoUploader.tsx @@ -0,0 +1,90 @@ +import React, { useRef } from 'react'; +import { Flex, Box, Icon, Text, Button, Input, AspectRatio, useColorModeValue } from '@chakra-ui/react'; +import { MdUpload } from 'react-icons/md'; +import { VideoSupportedFormatsEnum } from '@educt/enums'; + +type VideoUploaderPropsType = { + onChange: (file: File) => void; + file?: File | undefined; +}; + +const VideoUploader: React.FC = ({ onChange, file }) => { + const fileInputRef = useRef(null); + + const onSelectFileHandler = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + const onChangeFileHandler = (e: React.ChangeEvent) => { + e.preventDefault(); + + const { files } = e.target; + + if (files && files.length !== 0) { + const file = files[0]; + + file.type.match('video.*') && onChange(file); + } + }; + return ( + + `.${ext.toLowerCase()}`) + .join(', ')} + /> + {file ? ( + + + + + + + ) : ( + + + + + + + Upload a video file
+ + The video will be uploaded to cloud or local storage depending on the server configuration. + +
+
+ +
+ )} +
+ ); +}; + +export default VideoUploader; diff --git a/src/components/VideoUploader/index.ts b/src/components/VideoUploader/index.ts new file mode 100644 index 0000000..a91e97c --- /dev/null +++ b/src/components/VideoUploader/index.ts @@ -0,0 +1,3 @@ +import VideoUploader from './VideoUploader'; + +export default VideoUploader; diff --git a/src/enums/index.ts b/src/enums/index.ts index c3fc012..5efc3ef 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -8,3 +8,19 @@ export enum CourseStatusEnum { PUBLISHED = 'PUBLISHED', DRAFT = 'DRAFT', } + +export enum VideoSupportedFormatsEnum { + MP4 = 'video/mp4', + MOV = 'video/quicktime', + AVI = 'video/x-msvideo', + WMV = 'video/x-ms-wmv', + FLV = 'video/x-flv', +} + +export enum FileSupportedFormatsEnum { + PDF = 'application/pdf', + ZIP = 'application/zip', + RAR = 'application/x-rar-compressed', + DOC = 'application/msword', + DOCX = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', +} diff --git a/src/pages/courses/create-lesson/create-lesson.tsx b/src/pages/courses/create-lesson/create-lesson.tsx index e0e9d04..bd19b90 100644 --- a/src/pages/courses/create-lesson/create-lesson.tsx +++ b/src/pages/courses/create-lesson/create-lesson.tsx @@ -22,7 +22,7 @@ const CreateLessonPage: React.FC = () => { return ( - +