diff --git a/src/components/media/MediaFormUtils.tsx b/src/components/media/MediaFormUtils.tsx new file mode 100644 index 0000000..0bf9005 --- /dev/null +++ b/src/components/media/MediaFormUtils.tsx @@ -0,0 +1,85 @@ +import { useState } from 'react' +import { UseFormSetError, UseFormClearErrors, UseFormSetValue } from 'react-hook-form' + +export const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] +export const ALLOWED_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/ogg'] + +export const validateFileType = (file: File, mediaType: number) => { + if (mediaType === 1 && !ALLOWED_IMAGE_TYPES.includes(file.type)) { + return 'Для типа "Фото" разрешены только форматы: JPG, PNG, GIF, WEBP' + } + + if (mediaType === 2 && !ALLOWED_VIDEO_TYPES.includes(file.type)) { + return 'Для типа "Видео" разрешены только форматы: MP4, WEBM, OGG' + } + + return null +} + +type UseMediaFileUploadProps = { + selectedMediaType: number + setError: UseFormSetError + clearErrors: UseFormClearErrors + setValue: UseFormSetValue +} + +export const useMediaFileUpload = ({ + selectedMediaType, + setError, + clearErrors, + setValue, +}: UseMediaFileUploadProps) => { + const [selectedFile, setSelectedFile] = useState(null) + const [previewUrl, setPreviewUrl] = useState(null) + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (!file) return + + if (selectedMediaType) { + const error = validateFileType(file, selectedMediaType) + if (error) { + setError('file', { type: 'manual', message: error }) + event.target.value = '' + return + } + } + + clearErrors('file') + setValue('file', file) + setSelectedFile(file) + + if (file.type.startsWith('image/')) { + const url = URL.createObjectURL(file) + setPreviewUrl(url) + } else { + setPreviewUrl(null) + } + } + + const handleMediaTypeChange = (event: any) => { + const newMediaType = event.target.value + setValue('media_type', newMediaType) + + if (selectedFile) { + const error = validateFileType(selectedFile, newMediaType) + if (error) { + setError('file', { type: 'manual', message: error }) + setValue('file', null) + setSelectedFile(null) + setPreviewUrl(null) + } else { + clearErrors('file') + } + } + } + + return { + selectedFile, + setSelectedFile, + previewUrl, + setPreviewUrl, + handleFileChange, + handleMediaTypeChange, + } +} \ No newline at end of file diff --git a/src/globals.css b/src/globals.css index a0c313f..78723b1 100644 --- a/src/globals.css +++ b/src/globals.css @@ -1,2 +1,2 @@ -@import './stylesheets/hidden-functionality'; -@import './stylesheets/markdown-editor'; +@import './stylesheets/hidden-functionality.css'; +@import './stylesheets/markdown-editor.css'; diff --git a/src/pages/city/create.tsx b/src/pages/city/create.tsx index 09bdd63..6c94212 100644 --- a/src/pages/city/create.tsx +++ b/src/pages/city/create.tsx @@ -52,7 +52,7 @@ export const CityCreate = () => { fullWidth InputLabelProps={{shrink: true}} type="text" - label={'Название'} + label={'Название *'} name="name" /> diff --git a/src/pages/media/create.tsx b/src/pages/media/create.tsx index 00543f0..095663a 100644 --- a/src/pages/media/create.tsx +++ b/src/pages/media/create.tsx @@ -1,9 +1,9 @@ import {Box, TextField, Button, Typography, FormControl, InputLabel, Select, MenuItem} from '@mui/material' import {Create} from '@refinedev/mui' import {useForm} from '@refinedev/react-hook-form' -import {useState} from 'react' import {MEDIA_TYPES} from '../../lib/constants' +import {ALLOWED_IMAGE_TYPES, ALLOWED_VIDEO_TYPES, useMediaFileUpload} from '../../components/media/MediaFormUtils' export const MediaCreate = () => { const { @@ -14,52 +14,42 @@ export const MediaCreate = () => { setValue, handleSubmit, watch, + setError, + clearErrors, } = useForm({}) - const [selectedFile, setSelectedFile] = useState(null) - const [previewUrl, setPreviewUrl] = useState(null) + const selectedMediaType = watch('media_type') - const handleFileChange = (event: React.ChangeEvent) => { - const file = event.target.files?.[0] - if (file) { - setValue('file', file) - setSelectedFile(file) - - if (file.type.startsWith('image/')) { - const url = URL.createObjectURL(file) - setPreviewUrl(url) - } else { - setPreviewUrl(null) - } - } - } + const {selectedFile, previewUrl, handleFileChange, handleMediaTypeChange} = useMediaFileUpload({ + selectedMediaType, + setError, + clearErrors, + setValue, + }) return ( { const formData = new FormData() - formData.append('filename', data.filename) formData.append('type', String(data.media_type)) - - const file = watch('file') - if (file) { - formData.append('file', file) + if (data.file) { + formData.append('file', data.file) } - onFinish(formData) }), }} > - - {selectedFile && ( @@ -67,6 +57,12 @@ export const MediaCreate = () => { {selectedFile.name} )} + + {errors.file && ( + + {(errors as any)?.file?.message} + + )} {previewUrl && ( @@ -76,6 +72,30 @@ export const MediaCreate = () => { )} + + Тип + + {errors.media_type && ( + + {(errors as any)?.media_type?.message} + + )} + + { label="Название" name="filename" /> - - - Тип - - {errors.media_type && ( - - {!!(errors as any)?.message} - - )} - ) diff --git a/src/pages/media/edit.tsx b/src/pages/media/edit.tsx index 024423a..2a79cf8 100644 --- a/src/pages/media/edit.tsx +++ b/src/pages/media/edit.tsx @@ -1,10 +1,11 @@ import {Box, TextField, Button, Typography, FormControl, InputLabel, Select, MenuItem} from '@mui/material' import {Edit} from '@refinedev/mui' import {useForm} from '@refinedev/react-hook-form' -import {useState, useEffect} from 'react' +import {useEffect} from 'react' import {useShow} from '@refinedev/core' import {MEDIA_TYPES} from '../../lib/constants' +import {ALLOWED_IMAGE_TYPES, ALLOWED_VIDEO_TYPES, useMediaFileUpload} from '../../components/media/MediaFormUtils' type MediaFormValues = { filename: string @@ -20,6 +21,9 @@ export const MediaEdit = () => { formState: {errors}, setValue, handleSubmit, + watch, + setError, + clearErrors, } = useForm({ defaultValues: { filename: '', @@ -32,8 +36,14 @@ export const MediaEdit = () => { const {data} = query const record = data?.data - const [selectedFile, setSelectedFile] = useState(null) - const [previewUrl, setPreviewUrl] = useState(null) + const selectedMediaType = watch('media_type') + + const {selectedFile, previewUrl, setPreviewUrl, handleFileChange, handleMediaTypeChange} = useMediaFileUpload({ + selectedMediaType, + setError, + clearErrors, + setValue, + }) useEffect(() => { if (record?.id) { @@ -41,41 +51,28 @@ export const MediaEdit = () => { setValue('filename', record?.filename || '') setValue('media_type', record?.media_type) } - }, [record, setValue]) - - const handleFileChange = (event: React.ChangeEvent) => { - const file = event.target.files?.[0] - if (file) { - setValue('file', file) - setSelectedFile(file) - - if (file.type.startsWith('image/')) { - const url = URL.createObjectURL(file) - setPreviewUrl(url) - } - } - } + }, [record, setValue, setPreviewUrl]) return ( { const formData = { filename: data.filename, type: Number(data.media_type), } - onFinish(formData) }), }} > - - {selectedFile && ( @@ -83,6 +80,12 @@ export const MediaEdit = () => { {selectedFile.name} )} + + {errors.file && ( + + {(errors as any)?.file?.message} + + )} {previewUrl && ( @@ -92,6 +95,30 @@ export const MediaEdit = () => { )} + + Тип + + {errors.media_type && ( + + {(errors as any)?.media_type?.message} + + )} + + { label="Название" name="filename" /> - - - Тип - - - {errors.media_type && ( - - {(errors as any)?.message} - - )} - )