multi upload icons
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-14 09:51:57 +03:30
parent 328402fd7f
commit 35c1c1c8a9
3 changed files with 106 additions and 69 deletions
+32 -3
View File
@@ -12,6 +12,34 @@ type Props = {
accept?: string;
}
const MIME_TO_EXTENSIONS: Record<string, string[]> = {
'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<Record<string, string[]>>((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: Props) => {
const { t } = useTranslation('global')
@@ -19,8 +47,7 @@ const UploadBox: FC<Props> = (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: 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) => {
+37 -33
View File
@@ -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<CreateIconProps> = ({ open, close, onSuccess }) => {
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const [files, setFiles] = useState<File[]>([])
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<CreateIconProps> = ({ 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<CreateIconType>({
initialValues: {
url: '',
@@ -57,27 +43,44 @@ const CreateIcon: FC<CreateIconProps> = ({ 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<CreateIconProps> = ({ open, close, onSuccess }) => {
<div className='mt-6'>
<UploadBox
label={t('icon.url')}
onChange={(files) => setFile(files[0])}
onChange={(selectedFiles) => setFiles(selectedFiles)}
isReset={!open}
accept="image/svg+xml"
isMultiple
accept="image/svg+xml,.svg,image/png,.png"
/>
<div className='mt-4'>
@@ -117,7 +121,7 @@ const CreateIcon: FC<CreateIconProps> = ({ open, close, onSuccess }) => {
<Button
onClick={() => formik.handleSubmit()}
isLoading={createIcon.isPending || singleUpload.isPending}
isLoading={isSubmitting}
>
<div className='flex gap-2'>
<TickCircle
+37 -33
View File
@@ -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<CreateIconProps> = ({ open, close, onSuccess }) => {
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const [files, setFiles] = useState<File[]>([])
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<CreateIconProps> = ({ 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<CreateIconType>({
initialValues: {
url: '',
@@ -57,27 +43,44 @@ const CreateIcon: FC<CreateIconProps> = ({ 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<CreateIconProps> = ({ open, close, onSuccess }) => {
<div className='mt-6'>
<UploadBox
label={t('icon.url')}
onChange={(files) => setFile(files[0])}
onChange={(selectedFiles) => setFiles(selectedFiles)}
isReset={!open}
accept="image/svg+xml"
isMultiple
accept="image/svg+xml,.svg,image/png,.png"
/>
<div className='mt-4'>
@@ -117,7 +121,7 @@ const CreateIcon: FC<CreateIconProps> = ({ open, close, onSuccess }) => {
<Button
onClick={() => formik.handleSubmit()}
isLoading={createIcon.isPending || singleUpload.isPending}
isLoading={isSubmitting}
>
<div className='flex gap-2'>
<TickCircle