diff --git a/src/components/UploadBox.tsx b/src/components/UploadBox.tsx index 95b6eb5..4ca6b4c 100644 --- a/src/components/UploadBox.tsx +++ b/src/components/UploadBox.tsx @@ -12,6 +12,34 @@ type Props = { accept?: string; } +const MIME_TO_EXTENSIONS: Record = { + 'image/png': ['.png'], + 'image/svg+xml': ['.svg'], + 'image/jpeg': ['.jpg', '.jpeg'], + 'image/gif': ['.gif'], + 'image/webp': ['.webp'], +} + +const buildAcceptTypes = (accept?: string) => { + if (!accept) return undefined + + return accept.split(',').reduce>((acc, type) => { + const trimmed = type.trim() + if (trimmed.startsWith('.')) { + const ext = trimmed.toLowerCase() + const mimeEntry = Object.entries(MIME_TO_EXTENSIONS).find(([, exts]) => exts.includes(ext)) + if (mimeEntry) { + acc[mimeEntry[0]] = mimeEntry[1] + } + } else if (MIME_TO_EXTENSIONS[trimmed]) { + acc[trimmed] = MIME_TO_EXTENSIONS[trimmed] + } else { + acc[trimmed] = [] + } + return acc + }, {}) +} + const UploadBox: FC = (props: Props) => { const { t } = useTranslation('global') @@ -19,8 +47,7 @@ const UploadBox: FC = (props: Props) => { const onDrop = useCallback((acceptedFiles: File[]) => { if (props.isMultiple) { - const array = [...files] - array.push(acceptedFiles[0]) + const array = [...files, ...acceptedFiles] setFiles(array) if (props.onChange) { props.onChange(array); @@ -33,9 +60,11 @@ const UploadBox: FC = (props: Props) => { } // eslint-disable-next-line react-hooks/exhaustive-deps }, [files]) + const acceptTypes = buildAcceptTypes(props.accept) const { getRootProps, getInputProps } = useDropzone({ onDrop, - accept: props.accept ? { [props.accept]: [] } : undefined + accept: acceptTypes, + multiple: props.isMultiple, }) const handleRemove = (index: number) => { diff --git a/src/pages/dkala/icon/components/CreateIcon.tsx b/src/pages/dkala/icon/components/CreateIcon.tsx index 0010aa8..24c452c 100644 --- a/src/pages/dkala/icon/components/CreateIcon.tsx +++ b/src/pages/dkala/icon/components/CreateIcon.tsx @@ -9,7 +9,7 @@ import * as Yup from 'yup' import { toast } from 'react-toastify' import { TickCircle } from 'iconsax-react' import { useCreateIcon, useGetGroupIcons } from '../../hooks/useIconData' -import { useSingleUpload } from '../../../service/hooks/useServiceData' +import { useMultiUpload } from '../../../service/hooks/useServiceData' import { CreateIconType, GroupIconType } from '../../types/Types' import { ErrorType } from '../../../../helpers/types' @@ -21,9 +21,10 @@ interface CreateIconProps { const CreateIcon: FC = ({ open, close, onSuccess }) => { const { t } = useTranslation('global') - const [file, setFile] = useState() + const [files, setFiles] = useState([]) + const [isSubmitting, setIsSubmitting] = useState(false) const createIcon = useCreateIcon() - const singleUpload = useSingleUpload() + const multiUpload = useMultiUpload() const { data: groupIcons } = useGetGroupIcons() const groups = (groupIcons as unknown as { data?: GroupIconType[] })?.data || [] @@ -32,21 +33,6 @@ const CreateIcon: FC = ({ open, close, onSuccess }) => { label: group.name })) - const handleCreateIcon = (values: CreateIconType) => { - createIcon.mutate(values, { - onSuccess: () => { - formik.resetForm() - setFile(undefined) - close() - toast.success(t('success')) - onSuccess?.() - }, - onError: (error: ErrorType) => { - toast.error(error.response?.data?.error?.message[0]) - } - }) - } - const formik = useFormik({ initialValues: { url: '', @@ -57,27 +43,44 @@ const CreateIcon: FC = ({ open, close, onSuccess }) => { .required(t('errors.required')) }), onSubmit: async (values) => { - if (!file) { + if (files.length === 0) { toast.error(t('errors.required')) return } - const formData = new FormData() - formData.append('file', file) - await singleUpload.mutateAsync(formData, { - onSuccess: (data) => { - values.url = data?.data?.url - handleCreateIcon(values) - }, - onError: (error: ErrorType) => { - toast.error(error.response?.data?.error?.message[0]) + + setIsSubmitting(true) + try { + const formData = new FormData() + files.forEach((file) => { + formData.append('files', file) + }) + const uploadResult = await multiUpload.mutateAsync(formData) + const uploadedUrls: string[] = uploadResult.data.map((item: { url: string }) => item.url) + + for (const url of uploadedUrls) { + await createIcon.mutateAsync({ + url, + groupId: values.groupId + }) } - }) + + formik.resetForm() + setFiles([]) + close() + toast.success(t('success')) + onSuccess?.() + } catch (error) { + const err = error as ErrorType + toast.error(err.response?.data?.error?.message?.[0]) + } finally { + setIsSubmitting(false) + } } }) const handleClose = () => { formik.resetForm() - setFile(undefined) + setFiles([]) close() } @@ -91,9 +94,10 @@ const CreateIcon: FC = ({ open, close, onSuccess }) => {
setFile(files[0])} + onChange={(selectedFiles) => setFiles(selectedFiles)} isReset={!open} - accept="image/svg+xml" + isMultiple + accept="image/svg+xml,.svg,image/png,.png" />
@@ -117,7 +121,7 @@ const CreateIcon: FC = ({ open, close, onSuccess }) => {