mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: create course page layout
This commit is contained in:
parent
3799f150a8
commit
5604f2e85f
47
package-lock.json
generated
47
package-lock.json
generated
@ -1187,6 +1187,14 @@
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
|
||||
"integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
@ -1387,6 +1395,15 @@
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||
},
|
||||
"dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.870",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.870.tgz",
|
||||
@ -1834,6 +1851,11 @@
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"mini-create-react-context": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||
@ -2166,6 +2188,20 @@
|
||||
"tiny-warning": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.1.0.tgz",
|
||||
"integrity": "sha512-SkEBD1AYsSXrIdNj5HBt7+Ehe+jxdiB448J0atJqR6lE3l/GcFlRf4JYB3NlHe/02jrW4AnIQLo1t0IqWrxXOw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.0",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.1.1",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"memoize-one": "^5.0.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-transition-group": "^4.3.0"
|
||||
}
|
||||
},
|
||||
"react-side-effect": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.1.tgz",
|
||||
@ -2188,6 +2224,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
|
||||
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.9",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"react-hook-form": "^7.12.2",
|
||||
"react-icons": "^3.11.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-select": "^5.1.0",
|
||||
"validator": "^13.6.0",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
|
||||
@ -1,58 +1,147 @@
|
||||
import React from 'react';
|
||||
import { Box, Grid } from '@chakra-ui/react';
|
||||
import { Box, Grid, useToast } from '@chakra-ui/react';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { Select, Flex, Button, Textarea } from '@chakra-ui/react';
|
||||
import { Flex, Button, Textarea } from '@chakra-ui/react';
|
||||
import { FormControl, FormLabel, InputGroup, InputRightElement } from '@chakra-ui/react';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
import { MdCloudUpload } from 'react-icons/md';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { UserRoleEnum } from '@educt/enums';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { SubmitHandler, useForm, Controller } from 'react-hook-form';
|
||||
|
||||
type CreateCourseInputType = {
|
||||
title: string;
|
||||
image: File;
|
||||
category_id: string;
|
||||
description: string;
|
||||
teacher_id: string;
|
||||
};
|
||||
type CreateCourseFormProps = {};
|
||||
|
||||
const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
|
||||
const {
|
||||
userStore: { userService },
|
||||
categoryStore: { categoryService },
|
||||
} = useRootStore();
|
||||
const { register, formState, handleSubmit, control, reset } = useForm<CreateCourseInputType>();
|
||||
const handleError = useErrorHandler();
|
||||
const toast = useToast();
|
||||
|
||||
/**
|
||||
* Load users into select field
|
||||
*/
|
||||
const loadUsersOptions = async (inputValue: string) => {
|
||||
try {
|
||||
const users = await userService.fetchAll({ search: inputValue, role: UserRoleEnum.TEACHER });
|
||||
return users.data.map(user => ({
|
||||
label: user.fullname,
|
||||
value: user.id,
|
||||
}));
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
toast({ title: 'Error' });
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load categories into select filed
|
||||
*/
|
||||
const loadCategoriesOptions = async (inputValue: string) => {
|
||||
try {
|
||||
const categories = await categoryService.fetchAll();
|
||||
return categories.data.map(category => ({
|
||||
label: category.title,
|
||||
value: category.id,
|
||||
}));
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
toast({ title: 'Error' });
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit form handler
|
||||
*/
|
||||
const onSubmit: SubmitHandler<CreateCourseInputType> = async data => {
|
||||
console.log(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<form>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Grid templateColumns={{ md: '2.5fr 1fr' }} gap='5'>
|
||||
<FormControl id='title'>
|
||||
<FormLabel>Title</FormLabel>
|
||||
<InputGroup>
|
||||
<Input size='md' placeholder='Type course title' type='text' />
|
||||
<Input size='md' placeholder='Type course title' type='text' {...register('title')} />
|
||||
<InputRightElement fontSize='sm' mr='2' color='gray.500' children={'0/60'} />
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
<FormControl id='image'>
|
||||
<FormLabel>Image</FormLabel>
|
||||
<Input size='md' variant='unstyled' type='file' />
|
||||
<Input size='md' variant='unstyled' type='file' {...register('image')} />
|
||||
</FormControl>
|
||||
|
||||
<FormControl id='category' gridColumn='1'>
|
||||
<FormControl id='category_id' gridColumn='1'>
|
||||
<FormLabel>Category</FormLabel>
|
||||
<Select size='md' placeholder='Select category'>
|
||||
<option value='option1'>Option 1</option>
|
||||
<option value='option2'>Option 2</option>
|
||||
<option value='option3'>Option 3</option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl id='title' gridColumn={{ md: '2' }} gridRow={{ md: '1' }}>
|
||||
<Controller
|
||||
control={control}
|
||||
name='category_id'
|
||||
render={({ field: { onChange } }) => (
|
||||
<AsyncSelect
|
||||
defaultOptions
|
||||
cacheOptions
|
||||
loadOptions={loadCategoriesOptions}
|
||||
isSearchable={false}
|
||||
onChange={selected => onChange(selected?.value)}
|
||||
placeholder='Select Category'
|
||||
loadingMessage={() => 'Loading...'}
|
||||
noOptionsMessage={() => 'There are no categories.'}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl id='teacher_id' gridColumn={{ md: '2' }} gridRow={{ md: '1' }}>
|
||||
<FormLabel>Teacher</FormLabel>
|
||||
<Button w='full'>Select Teacher</Button>
|
||||
<Controller
|
||||
control={control}
|
||||
name='teacher_id'
|
||||
render={({ field: { onChange } }) => (
|
||||
<AsyncSelect
|
||||
defaultOptions
|
||||
cacheOptions
|
||||
loadOptions={loadUsersOptions}
|
||||
onChange={selected => onChange(selected?.value)}
|
||||
placeholder='Select Teacher'
|
||||
loadingMessage={() => 'Loading...'}
|
||||
noOptionsMessage={() => 'Cannot find any teacher.'}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl id='description' gridColumn='1'>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<Textarea resize='none' minH='150px' placeholder='Write some description for course...' />
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Flex mt='6'>
|
||||
<Button mr='2' colorScheme='blue' variant='outline' rightIcon={<MdCloudUpload size='14px' />}>
|
||||
<Button type='submit' mr='2' colorScheme='blue' variant='outline' rightIcon={<MdCloudUpload size='14px' />}>
|
||||
Save as Draft
|
||||
</Button>
|
||||
<Button colorScheme='green'>Publish</Button>
|
||||
<Button type='submit' colorScheme='green'>
|
||||
Publish
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
</Box>
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import yup from '@educt/schema';
|
||||
|
||||
const MAX_FILE_SIZE = 300 * 1024;
|
||||
const SUPPORTED_FORMATS = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png'];
|
||||
|
||||
const CreateCourseSchema = yup.object().shape({
|
||||
title: yup.string().required('Title field name is required'),
|
||||
description: yup.string().required('Description field name is required'),
|
||||
image: yup
|
||||
.mixed()
|
||||
.test('fileSize', 'File size is too large', (value: File) => value && value.size >= MAX_FILE_SIZE)
|
||||
.test('fileFormat', 'Usupported file format', (value: File) => value && SUPPORTED_FORMATS.includes(value.type))
|
||||
.optional(),
|
||||
category_id: yup.string().required('Category field is required'),
|
||||
teacher_id: yup.string().required('Teacher field is required'),
|
||||
});
|
||||
|
||||
export default CreateCourseSchema;
|
||||
Loading…
x
Reference in New Issue
Block a user