fix footer upload
This commit is contained in:
@@ -52,9 +52,9 @@ const SiteSetting: FC = () => {
|
|||||||
const { data: shopData, isLoading: isShopLoading } = useGetShop()
|
const { data: shopData, isLoading: isShopLoading } = useGetShop()
|
||||||
const updateSiteSetting = useUpdateSiteSetting()
|
const updateSiteSetting = useUpdateSiteSetting()
|
||||||
const [isDataLoaded, setIsDataLoaded] = useState(false)
|
const [isDataLoaded, setIsDataLoaded] = useState(false)
|
||||||
const [uploadingIndexes, setUploadingIndexes] = useState<Set<number>>(new Set())
|
|
||||||
const [pendingFiles, setPendingFiles] = useState<{ [key: string]: File }>({})
|
|
||||||
const singleUpload = useSingleUpload()
|
const singleUpload = useSingleUpload()
|
||||||
|
const [logoFile, setLogoFile] = useState<File[]>([])
|
||||||
|
const [postalCodeFile, setPostalCodeFile] = useState<File[]>([])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -73,34 +73,31 @@ const SiteSetting: FC = () => {
|
|||||||
validateOnChange: false,
|
validateOnChange: false,
|
||||||
validateOnBlur: false,
|
validateOnBlur: false,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
let finalDownloadAppDetails = values.downloadAppDetails
|
let siteLogo = values.siteLogo
|
||||||
let finalSocialMediaLinks = values.socialMediaLinks
|
let shopPostalCode = values.shopPostalCode
|
||||||
|
|
||||||
// اول همه فایلهای pending رو آپلود کن
|
// آپلود لوگو سایت اگر فایل جدید انتخاب شده
|
||||||
if (Object.keys(pendingFiles).length > 0) {
|
if (logoFile.length > 0) {
|
||||||
const uploadedData = await uploadAllPendingFiles()
|
const logoResponse = await singleUpload.mutateAsync(logoFile[0])
|
||||||
// بروزرسانی downloadAppDetails با URL های واقعی
|
siteLogo = logoResponse.results?.url?.url || undefined
|
||||||
finalDownloadAppDetails = values.downloadAppDetails.map(item => {
|
}
|
||||||
const uploadedUrl = uploadedData[item.id]
|
|
||||||
return uploadedUrl ? { ...item, pic: uploadedUrl } : item
|
// آپلود کد پستی فروشگاه اگر فایل جدید انتخاب شده
|
||||||
})
|
if (postalCodeFile.length > 0) {
|
||||||
// بروزرسانی socialMediaLinks با URL های واقعی
|
const postalResponse = await singleUpload.mutateAsync(postalCodeFile[0])
|
||||||
finalSocialMediaLinks = values.socialMediaLinks.map(item => {
|
shopPostalCode = postalResponse.results?.url?.url || undefined
|
||||||
const uploadedUrl = uploadedData[item.id]
|
|
||||||
return uploadedUrl ? { ...item, icon: uploadedUrl } : item
|
|
||||||
})
|
|
||||||
// پاک کردن pending files بعد از آپلود موفق
|
|
||||||
setPendingFiles({})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// transform data برای ارسال به API (حذف id)
|
// transform data برای ارسال به API (حذف id)
|
||||||
const transformedValues = {
|
const transformedValues = {
|
||||||
...values,
|
...values,
|
||||||
downloadAppDetails: finalDownloadAppDetails.map((item) => ({
|
siteLogo,
|
||||||
|
shopPostalCode,
|
||||||
|
downloadAppDetails: values.downloadAppDetails.map((item) => ({
|
||||||
pic: item.pic,
|
pic: item.pic,
|
||||||
url: item.url
|
url: item.url
|
||||||
})),
|
})),
|
||||||
socialMediaLinks: finalSocialMediaLinks.map((item) => ({
|
socialMediaLinks: values.socialMediaLinks.map((item) => ({
|
||||||
icon: item.icon,
|
icon: item.icon,
|
||||||
url: item.url
|
url: item.url
|
||||||
}))
|
}))
|
||||||
@@ -110,8 +107,8 @@ const SiteSetting: FC = () => {
|
|||||||
updateSiteSetting.mutate(transformedValues, {
|
updateSiteSetting.mutate(transformedValues, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('تنظیمات سایت با موفقیت بروزرسانی شد')
|
toast.success('تنظیمات سایت با موفقیت بروزرسانی شد')
|
||||||
// پاک کردن pending files بعد از submit موفق
|
setLogoFile([])
|
||||||
setPendingFiles({})
|
setPostalCodeFile([])
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(extractErrorMessage(error))
|
toast.error(extractErrorMessage(error))
|
||||||
@@ -151,64 +148,6 @@ const SiteSetting: FC = () => {
|
|||||||
}, [data, shopData, isLoading, isShopLoading])
|
}, [data, shopData, isLoading, isShopLoading])
|
||||||
|
|
||||||
|
|
||||||
const uploadAllPendingFiles = async (): Promise<{ [key: string]: string }> => {
|
|
||||||
const uploadedUrls: { [key: string]: string } = {}
|
|
||||||
|
|
||||||
for (const [id, file] of Object.entries(pendingFiles)) {
|
|
||||||
// پیدا کردن index آیتم در downloadAppDetails
|
|
||||||
let index = formik.values.downloadAppDetails?.findIndex(item => item.id === id)
|
|
||||||
let isDownloadApp = true
|
|
||||||
|
|
||||||
if (index === undefined || index === -1) {
|
|
||||||
// اگر در downloadAppDetails نبود، در socialMediaLinks جستجو کن
|
|
||||||
index = formik.values.socialMediaLinks?.findIndex(item => item.id === id)
|
|
||||||
isDownloadApp = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index === undefined || index === -1) continue
|
|
||||||
|
|
||||||
try {
|
|
||||||
setUploadingIndexes(prev => new Set(prev).add(index))
|
|
||||||
|
|
||||||
const uploadResponse = await singleUpload.mutateAsync(file)
|
|
||||||
const imageUrl = uploadResponse.results.url.url
|
|
||||||
|
|
||||||
uploadedUrls[id] = imageUrl
|
|
||||||
|
|
||||||
// بروزرسانی formik
|
|
||||||
if (isDownloadApp) {
|
|
||||||
const currentDetails = formik.values.downloadAppDetails || []
|
|
||||||
const updatedDetails = currentDetails.map((item, i) =>
|
|
||||||
i === index ? { ...item, pic: imageUrl } : item
|
|
||||||
)
|
|
||||||
formik.setFieldValue('downloadAppDetails', updatedDetails)
|
|
||||||
} else {
|
|
||||||
const currentLinks = formik.values.socialMediaLinks || []
|
|
||||||
const updatedLinks = currentLinks.map((item, i) =>
|
|
||||||
i === index ? { ...item, icon: imageUrl } : item
|
|
||||||
)
|
|
||||||
formik.setFieldValue('socialMediaLinks', updatedLinks)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
const itemType = isDownloadApp ? 'تصویر دانلود اپ' : 'آیکون رسانه اجتماعی'
|
|
||||||
toast.error(`خطا در آپلود ${itemType} آیتم ${index + 1}`)
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
setUploadingIndexes(prev => {
|
|
||||||
const newSet = new Set(prev)
|
|
||||||
newSet.delete(index)
|
|
||||||
return newSet
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return uploadedUrls
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (isLoading || isShopLoading) {
|
if (isLoading || isShopLoading) {
|
||||||
return <div className="flex justify-center items-center h-64">در حال بارگذاری...</div>
|
return <div className="flex justify-center items-center h-64">در حال بارگذاری...</div>
|
||||||
}
|
}
|
||||||
@@ -237,24 +176,18 @@ const SiteSetting: FC = () => {
|
|||||||
|
|
||||||
<div className='flex gap-6 xl:mt-8 mt-6'>
|
<div className='flex gap-6 xl:mt-8 mt-6'>
|
||||||
<MainInfoSection formik={formik} isDataLoaded={isDataLoaded} />
|
<MainInfoSection formik={formik} isDataLoaded={isDataLoaded} />
|
||||||
<FilesSection formik={formik} />
|
<FilesSection
|
||||||
|
formik={formik}
|
||||||
|
logoFile={logoFile}
|
||||||
|
setLogoFile={setLogoFile}
|
||||||
|
postalCodeFile={postalCodeFile}
|
||||||
|
setPostalCodeFile={setPostalCodeFile}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DownloadAppSection
|
<DownloadAppSection formik={formik} />
|
||||||
formik={formik}
|
|
||||||
pendingFiles={pendingFiles}
|
|
||||||
setPendingFiles={setPendingFiles}
|
|
||||||
uploadingIndexes={uploadingIndexes}
|
|
||||||
setUploadingIndexes={setUploadingIndexes}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SocialMediaSection
|
<SocialMediaSection formik={formik} />
|
||||||
formik={formik}
|
|
||||||
pendingFiles={pendingFiles}
|
|
||||||
setPendingFiles={setPendingFiles}
|
|
||||||
uploadingIndexes={uploadingIndexes}
|
|
||||||
setUploadingIndexes={setUploadingIndexes}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC, useState } from 'react'
|
||||||
import type { FormikProps } from 'formik'
|
import type { FormikProps } from 'formik'
|
||||||
import { Add, Trash } from 'iconsax-react'
|
import { Add, Trash } from 'iconsax-react'
|
||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import UploadBox from '../../../components/UploadBox'
|
import UploadBox from '../../../components/UploadBox'
|
||||||
|
import { useSingleUpload } from '../../category/hooks/useCategoryData'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
type DownloadAppDetail = {
|
type DownloadAppDetail = {
|
||||||
id: string
|
id: string
|
||||||
@@ -30,17 +32,13 @@ type FormValues = {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
formik: FormikProps<FormValues>
|
formik: FormikProps<FormValues>
|
||||||
pendingFiles: { [key: string]: File }
|
|
||||||
setPendingFiles: React.Dispatch<React.SetStateAction<{ [key: string]: File }>>
|
|
||||||
uploadingIndexes: Set<number>
|
|
||||||
setUploadingIndexes: React.Dispatch<React.SetStateAction<Set<number>>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DownloadAppSection: FC<Props> = ({
|
const DownloadAppSection: FC<Props> = ({
|
||||||
formik,
|
formik,
|
||||||
setPendingFiles,
|
|
||||||
uploadingIndexes,
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const singleUpload = useSingleUpload()
|
||||||
|
const [uploadingIndexes, setUploadingIndexes] = useState<Set<number>>(new Set())
|
||||||
const addDownloadAppDetail = () => {
|
const addDownloadAppDetail = () => {
|
||||||
const currentDetails = formik.values.downloadAppDetails || []
|
const currentDetails = formik.values.downloadAppDetails || []
|
||||||
const newId = Date.now().toString()
|
const newId = Date.now().toString()
|
||||||
@@ -49,18 +47,7 @@ const DownloadAppSection: FC<Props> = ({
|
|||||||
|
|
||||||
const removeDownloadAppDetail = (index: number) => {
|
const removeDownloadAppDetail = (index: number) => {
|
||||||
const currentDetails = formik.values.downloadAppDetails || []
|
const currentDetails = formik.values.downloadAppDetails || []
|
||||||
const itemToRemove = currentDetails[index]
|
|
||||||
|
|
||||||
formik.setFieldValue('downloadAppDetails', currentDetails.filter((_, i) => i !== index))
|
formik.setFieldValue('downloadAppDetails', currentDetails.filter((_, i) => i !== index))
|
||||||
|
|
||||||
// پاک کردن pending file مربوطه
|
|
||||||
if (itemToRemove) {
|
|
||||||
setPendingFiles(prev => {
|
|
||||||
const newPending = { ...prev }
|
|
||||||
delete newPending[itemToRemove.id]
|
|
||||||
return newPending
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateDownloadAppDetail = (index: number, field: 'pic' | 'url', value: string) => {
|
const updateDownloadAppDetail = (index: number, field: 'pic' | 'url', value: string) => {
|
||||||
@@ -71,20 +58,26 @@ const DownloadAppSection: FC<Props> = ({
|
|||||||
formik.setFieldValue('downloadAppDetails', updatedDetails)
|
formik.setFieldValue('downloadAppDetails', updatedDetails)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDownloadAppImageUpload = (index: number, files: File[]) => {
|
const handleDownloadAppImageUpload = async (index: number, files: File[]) => {
|
||||||
if (files.length === 0) return
|
if (files.length === 0) return
|
||||||
|
|
||||||
const item = formik.values.downloadAppDetails?.[index]
|
try {
|
||||||
if (!item) return
|
setUploadingIndexes(prev => new Set(prev).add(index))
|
||||||
|
|
||||||
// ذخیره فایل برای آپلود بعدی
|
const uploadResponse = await singleUpload.mutateAsync(files[0])
|
||||||
setPendingFiles(prev => ({
|
const imageUrl = uploadResponse.results.url.url
|
||||||
...prev,
|
|
||||||
[item.id]: files[0]
|
|
||||||
}))
|
|
||||||
|
|
||||||
// نمایش preview
|
updateDownloadAppDetail(index, 'pic', imageUrl)
|
||||||
updateDownloadAppDetail(index, 'pic', URL.createObjectURL(files[0]))
|
toast.success('تصویر با موفقیت آپلود شد')
|
||||||
|
} catch {
|
||||||
|
toast.error('خطا در آپلود تصویر')
|
||||||
|
} finally {
|
||||||
|
setUploadingIndexes(prev => {
|
||||||
|
const newSet = new Set(prev)
|
||||||
|
newSet.delete(index)
|
||||||
|
return newSet
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,9 +21,19 @@ type FormValues = {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
formik: FormikProps<FormValues>
|
formik: FormikProps<FormValues>
|
||||||
|
logoFile: File[]
|
||||||
|
setLogoFile: React.Dispatch<React.SetStateAction<File[]>>
|
||||||
|
postalCodeFile: File[]
|
||||||
|
setPostalCodeFile: React.Dispatch<React.SetStateAction<File[]>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilesSection: FC<Props> = ({ formik }) => {
|
const FilesSection: FC<Props> = ({
|
||||||
|
formik,
|
||||||
|
logoFile,
|
||||||
|
setLogoFile,
|
||||||
|
postalCodeFile,
|
||||||
|
setPostalCodeFile
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className='w-80 space-y-6'>
|
<div className='w-80 space-y-6'>
|
||||||
<div className='text-sm bg-white py-8 px-6 rounded-3xl'>
|
<div className='text-sm bg-white py-8 px-6 rounded-3xl'>
|
||||||
@@ -35,14 +45,14 @@ const FilesSection: FC<Props> = ({ formik }) => {
|
|||||||
<UploadBox
|
<UploadBox
|
||||||
label=""
|
label=""
|
||||||
isMultiple={false}
|
isMultiple={false}
|
||||||
onChange={(files) => {
|
onChange={(files) => setLogoFile(files)}
|
||||||
if (files.length > 0) {
|
|
||||||
formik.setFieldValue('siteLogo', URL.createObjectURL(files[0]))
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{formik.values.siteLogo && (
|
{(logoFile.length > 0 || formik.values.siteLogo) && (
|
||||||
<img src={formik.values.siteLogo} alt="لوگو سایت" className="mt-2 w-20 h-20 object-contain rounded" />
|
<img
|
||||||
|
src={logoFile.length > 0 ? URL.createObjectURL(logoFile[0]) : formik.values.siteLogo}
|
||||||
|
alt="لوگو سایت"
|
||||||
|
className="mt-2 w-20 h-20 object-contain rounded"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -51,14 +61,14 @@ const FilesSection: FC<Props> = ({ formik }) => {
|
|||||||
<UploadBox
|
<UploadBox
|
||||||
label=""
|
label=""
|
||||||
isMultiple={false}
|
isMultiple={false}
|
||||||
onChange={(files) => {
|
onChange={(files) => setPostalCodeFile(files)}
|
||||||
if (files.length > 0) {
|
|
||||||
formik.setFieldValue('shopPostalCode', URL.createObjectURL(files[0]))
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{formik.values.shopPostalCode && (
|
{(postalCodeFile.length > 0 || formik.values.shopPostalCode) && (
|
||||||
<img src={formik.values.shopPostalCode} alt="کد پستی فروشگاه" className="mt-2 w-20 h-20 object-contain rounded" />
|
<img
|
||||||
|
src={postalCodeFile.length > 0 ? URL.createObjectURL(postalCodeFile[0]) : formik.values.shopPostalCode}
|
||||||
|
alt="کد پستی فروشگاه"
|
||||||
|
className="mt-2 w-20 h-20 object-contain rounded"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC, useState } from 'react'
|
||||||
import type { FormikProps } from 'formik'
|
import type { FormikProps } from 'formik'
|
||||||
import { Add, Trash } from 'iconsax-react'
|
import { Add, Trash } from 'iconsax-react'
|
||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import UploadBox from '../../../components/UploadBox'
|
import UploadBox from '../../../components/UploadBox'
|
||||||
|
import { useSingleUpload } from '../../category/hooks/useCategoryData'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
type SocialMediaLink = {
|
type SocialMediaLink = {
|
||||||
id: string
|
id: string
|
||||||
@@ -24,17 +26,13 @@ type FormValues = {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
formik: FormikProps<FormValues>
|
formik: FormikProps<FormValues>
|
||||||
pendingFiles: { [key: string]: File }
|
|
||||||
setPendingFiles: React.Dispatch<React.SetStateAction<{ [key: string]: File }>>
|
|
||||||
uploadingIndexes: Set<number>
|
|
||||||
setUploadingIndexes: React.Dispatch<React.SetStateAction<Set<number>>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SocialMediaSection: FC<Props> = ({
|
const SocialMediaSection: FC<Props> = ({
|
||||||
formik,
|
formik,
|
||||||
setPendingFiles,
|
|
||||||
uploadingIndexes,
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const singleUpload = useSingleUpload()
|
||||||
|
const [uploadingIndexes, setUploadingIndexes] = useState<Set<number>>(new Set())
|
||||||
const addSocialMediaLink = () => {
|
const addSocialMediaLink = () => {
|
||||||
const currentLinks = formik.values.socialMediaLinks || []
|
const currentLinks = formik.values.socialMediaLinks || []
|
||||||
const newId = Date.now().toString()
|
const newId = Date.now().toString()
|
||||||
@@ -43,18 +41,7 @@ const SocialMediaSection: FC<Props> = ({
|
|||||||
|
|
||||||
const removeSocialMediaLink = (index: number) => {
|
const removeSocialMediaLink = (index: number) => {
|
||||||
const currentLinks = formik.values.socialMediaLinks || []
|
const currentLinks = formik.values.socialMediaLinks || []
|
||||||
const itemToRemove = currentLinks[index]
|
|
||||||
|
|
||||||
formik.setFieldValue('socialMediaLinks', currentLinks.filter((_, i) => i !== index))
|
formik.setFieldValue('socialMediaLinks', currentLinks.filter((_, i) => i !== index))
|
||||||
|
|
||||||
// پاک کردن pending file مربوطه
|
|
||||||
if (itemToRemove) {
|
|
||||||
setPendingFiles(prev => {
|
|
||||||
const newPending = { ...prev }
|
|
||||||
delete newPending[itemToRemove.id]
|
|
||||||
return newPending
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSocialMediaLink = (index: number, field: 'url', value: string) => {
|
const updateSocialMediaLink = (index: number, field: 'url', value: string) => {
|
||||||
@@ -65,20 +52,26 @@ const SocialMediaSection: FC<Props> = ({
|
|||||||
formik.setFieldValue('socialMediaLinks', updatedLinks)
|
formik.setFieldValue('socialMediaLinks', updatedLinks)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSocialMediaIconUpload = (index: number, files: File[]) => {
|
const handleSocialMediaIconUpload = async (index: number, files: File[]) => {
|
||||||
if (files.length === 0) return
|
if (files.length === 0) return
|
||||||
|
|
||||||
const item = formik.values.socialMediaLinks?.[index]
|
try {
|
||||||
if (!item) return
|
setUploadingIndexes(prev => new Set(prev).add(index))
|
||||||
|
|
||||||
// ذخیره فایل برای آپلود بعدی
|
const uploadResponse = await singleUpload.mutateAsync(files[0])
|
||||||
setPendingFiles(prev => ({
|
const imageUrl = uploadResponse.results.url.url
|
||||||
...prev,
|
|
||||||
[item.id]: files[0]
|
|
||||||
}))
|
|
||||||
|
|
||||||
// نمایش preview
|
updateSocialMediaLinkIcon(index, imageUrl)
|
||||||
updateSocialMediaLinkIcon(index, URL.createObjectURL(files[0]))
|
toast.success('آیکون با موفقیت آپلود شد')
|
||||||
|
} catch {
|
||||||
|
toast.error('خطا در آپلود آیکون')
|
||||||
|
} finally {
|
||||||
|
setUploadingIndexes(prev => {
|
||||||
|
const newSet = new Set(prev)
|
||||||
|
newSet.delete(index)
|
||||||
|
return newSet
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSocialMediaLinkIcon = (index: number, iconUrl: string) => {
|
const updateSocialMediaLinkIcon = (index: number, iconUrl: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user