site setting

This commit is contained in:
hamid zarghami
2025-10-04 08:43:16 +03:30
parent 2d52076ac0
commit 7db97eed6d
5 changed files with 160 additions and 32 deletions
+64 -12
View File
@@ -2,7 +2,6 @@ import { type FC, useEffect, useState } from 'react'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import * as Yup from 'yup' import * as Yup from 'yup'
import { useGetSiteSetting, useUpdateSiteSetting, useGetShop } from './hooks/useSettingData' import { useGetSiteSetting, useUpdateSiteSetting, useGetShop } from './hooks/useSettingData'
import { uploadSingle } from '../uploader/service/UploaderService'
type DownloadAppDetail = { type DownloadAppDetail = {
id: string id: string
@@ -10,6 +9,12 @@ type DownloadAppDetail = {
url: string url: string
} }
type SocialMediaLink = {
id: string
icon: string
url: string
}
type FormValues = { type FormValues = {
siteLogo?: string siteLogo?: string
footerPhone: string footerPhone: string
@@ -18,7 +23,7 @@ type FormValues = {
footerDescription: string footerDescription: string
shopPostalCode?: string shopPostalCode?: string
downloadAppDetails: DownloadAppDetail[] downloadAppDetails: DownloadAppDetail[]
socialMediaLinks: { icon: string; url: string }[] socialMediaLinks: SocialMediaLink[]
} }
import Button from '../../components/Button' import Button from '../../components/Button'
import { TickCircle } from 'iconsax-react' import { TickCircle } from 'iconsax-react'
@@ -28,6 +33,7 @@ import DownloadAppSection from './components/DownloadAppSection'
import SocialMediaSection from './components/SocialMediaSection' import SocialMediaSection from './components/SocialMediaSection'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { extractErrorMessage } from '../../helpers/utils' import { extractErrorMessage } from '../../helpers/utils'
import { useSingleUpload } from '../category/hooks/useCategoryData'
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
footerPhone: Yup.string().trim().required('تلفن فوتر الزامی است'), footerPhone: Yup.string().trim().required('تلفن فوتر الزامی است'),
@@ -41,12 +47,14 @@ const validationSchema = Yup.object().shape({
}) })
const SiteSetting: FC = () => { const SiteSetting: FC = () => {
const { data, isLoading } = useGetSiteSetting() const { data, isLoading } = useGetSiteSetting()
const { data: shopData } = useGetShop() const { data: shopData } = 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 [uploadingIndexes, setUploadingIndexes] = useState<Set<number>>(new Set())
const [pendingFiles, setPendingFiles] = useState<{ [key: string]: File }>({}) const [pendingFiles, setPendingFiles] = useState<{ [key: string]: File }>({})
const singleUpload = useSingleUpload()
const formik = useFormik<FormValues>({ const formik = useFormik<FormValues>({
initialValues: { initialValues: {
@@ -57,15 +65,28 @@ const SiteSetting: FC = () => {
footerDescription: '', footerDescription: '',
shopPostalCode: '', shopPostalCode: '',
downloadAppDetails: [], downloadAppDetails: [],
socialMediaLinks: [] socialMediaLinks: [] as SocialMediaLink[]
}, },
validationSchema, validationSchema,
validateOnChange: false, validateOnChange: false,
validateOnBlur: false, validateOnBlur: false,
onSubmit: async (values) => { onSubmit: async (values) => {
let finalDownloadAppDetails = values.downloadAppDetails
let finalSocialMediaLinks = values.socialMediaLinks
// اول همه فایل‌های pending رو آپلود کن // اول همه فایل‌های pending رو آپلود کن
if (Object.keys(pendingFiles).length > 0) { if (Object.keys(pendingFiles).length > 0) {
await uploadAllPendingFiles() const uploadedData = await uploadAllPendingFiles()
// بروزرسانی downloadAppDetails با URL های واقعی
finalDownloadAppDetails = values.downloadAppDetails.map(item => {
const uploadedUrl = uploadedData[item.id]
return uploadedUrl ? { ...item, pic: uploadedUrl } : item
})
// بروزرسانی socialMediaLinks با URL های واقعی
finalSocialMediaLinks = values.socialMediaLinks.map(item => {
const uploadedUrl = uploadedData[item.id]
return uploadedUrl ? { ...item, icon: uploadedUrl } : item
})
// پاک کردن pending files بعد از آپلود موفق // پاک کردن pending files بعد از آپلود موفق
setPendingFiles({}) setPendingFiles({})
} }
@@ -73,9 +94,13 @@ const SiteSetting: FC = () => {
// transform data برای ارسال به API (حذف id) // transform data برای ارسال به API (حذف id)
const transformedValues = { const transformedValues = {
...values, ...values,
downloadAppDetails: values.downloadAppDetails.map((item) => ({ downloadAppDetails: finalDownloadAppDetails.map((item) => ({
pic: item.pic, pic: item.pic,
url: item.url url: item.url
})),
socialMediaLinks: finalSocialMediaLinks.map((item) => ({
icon: item.icon,
url: item.url
})) }))
} }
@@ -107,7 +132,11 @@ const SiteSetting: FC = () => {
pic: item.pic, pic: item.pic,
url: item.url url: item.url
})), })),
socialMediaLinks: data.results.siteSetting.socialMediaLinks || [] socialMediaLinks: (data.results.siteSetting.socialMediaLinks || []).map((item: { icon: string; url: string }, index: number) => ({
id: `existing-${index}-${Date.now()}`,
icon: item.icon,
url: item.url
}))
}, false) }, false)
setIsDataLoaded(true) setIsDataLoaded(true)
} }
@@ -119,27 +148,44 @@ const SiteSetting: FC = () => {
const uploadedUrls: { [key: string]: string } = {} const uploadedUrls: { [key: string]: string } = {}
for (const [id, file] of Object.entries(pendingFiles)) { for (const [id, file] of Object.entries(pendingFiles)) {
// پیدا کردن index آیتم بر اساس id // پیدا کردن index آیتم در downloadAppDetails
const index = formik.values.downloadAppDetails?.findIndex(item => item.id === id) 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 if (index === undefined || index === -1) continue
try { try {
setUploadingIndexes(prev => new Set(prev).add(index)) setUploadingIndexes(prev => new Set(prev).add(index))
const uploadResponse = await uploadSingle(file) const uploadResponse = await singleUpload.mutateAsync(file)
const imageUrl = uploadResponse.results.url const imageUrl = uploadResponse.results.url.url
uploadedUrls[id] = imageUrl uploadedUrls[id] = imageUrl
// بروزرسانی formik // بروزرسانی formik
if (isDownloadApp) {
const currentDetails = formik.values.downloadAppDetails || [] const currentDetails = formik.values.downloadAppDetails || []
const updatedDetails = currentDetails.map((item, i) => const updatedDetails = currentDetails.map((item, i) =>
i === index ? { ...item, pic: imageUrl } : item i === index ? { ...item, pic: imageUrl } : item
) )
formik.setFieldValue('downloadAppDetails', updatedDetails) 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) { } catch (error) {
toast.error(`خطا در آپلود تصویر آیتم ${index + 1}`) const itemType = isDownloadApp ? 'تصویر دانلود اپ' : 'آیکون رسانه اجتماعی'
toast.error(`خطا در آپلود ${itemType} آیتم ${index + 1}`)
throw error throw error
} finally { } finally {
setUploadingIndexes(prev => { setUploadingIndexes(prev => {
@@ -192,7 +238,13 @@ const SiteSetting: FC = () => {
setUploadingIndexes={setUploadingIndexes} setUploadingIndexes={setUploadingIndexes}
/> />
<SocialMediaSection formik={formik} /> <SocialMediaSection
formik={formik}
pendingFiles={pendingFiles}
setPendingFiles={setPendingFiles}
uploadingIndexes={uploadingIndexes}
setUploadingIndexes={setUploadingIndexes}
/>
</div> </div>
) )
} }
@@ -11,6 +11,12 @@ type DownloadAppDetail = {
url: string url: string
} }
type SocialMediaLink = {
id: string
icon: string
url: string
}
type FormValues = { type FormValues = {
siteLogo?: string siteLogo?: string
footerPhone: string footerPhone: string
@@ -19,7 +25,7 @@ type FormValues = {
footerDescription: string footerDescription: string
shopPostalCode?: string shopPostalCode?: string
downloadAppDetails: DownloadAppDetail[] downloadAppDetails: DownloadAppDetail[]
socialMediaLinks: { icon: string; url: string }[] socialMediaLinks: SocialMediaLink[]
} }
type Props = { type Props = {
@@ -32,10 +38,8 @@ type Props = {
const DownloadAppSection: FC<Props> = ({ const DownloadAppSection: FC<Props> = ({
formik, formik,
pendingFiles,
setPendingFiles, setPendingFiles,
uploadingIndexes, uploadingIndexes,
setUploadingIndexes
}) => { }) => {
const addDownloadAppDetail = () => { const addDownloadAppDetail = () => {
const currentDetails = formik.values.downloadAppDetails || [] const currentDetails = formik.values.downloadAppDetails || []
@@ -2,6 +2,12 @@ import { type FC } from 'react'
import type { FormikProps } from 'formik' import type { FormikProps } from 'formik'
import UploadBox from '../../../components/UploadBox' import UploadBox from '../../../components/UploadBox'
type SocialMediaLink = {
id: string
icon: string
url: string
}
type FormValues = { type FormValues = {
siteLogo?: string siteLogo?: string
footerPhone: string footerPhone: string
@@ -10,7 +16,7 @@ type FormValues = {
footerDescription: string footerDescription: string
shopPostalCode?: string shopPostalCode?: string
downloadAppDetails: { id: string; pic: string; url: string }[] downloadAppDetails: { id: string; pic: string; url: string }[]
socialMediaLinks: { icon: string; url: string }[] socialMediaLinks: SocialMediaLink[]
} }
type Props = { type Props = {
@@ -4,6 +4,12 @@ import Input from '../../../components/Input'
import Textarea from '../../../components/Textarea' import Textarea from '../../../components/Textarea'
import TextEditor from '../../../components/TextEditor' import TextEditor from '../../../components/TextEditor'
type SocialMediaLink = {
id: string
icon: string
url: string
}
type FormValues = { type FormValues = {
siteLogo?: string siteLogo?: string
footerPhone: string footerPhone: string
@@ -12,7 +18,7 @@ type FormValues = {
footerDescription: string footerDescription: string
shopPostalCode?: string shopPostalCode?: string
downloadAppDetails: { id: string; pic: string; url: string }[] downloadAppDetails: { id: string; pic: string; url: string }[]
socialMediaLinks: { icon: string; url: string }[] socialMediaLinks: SocialMediaLink[]
} }
type Props = { type Props = {
@@ -3,6 +3,13 @@ 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'
type SocialMediaLink = {
id: string
icon: string
url: string
}
type FormValues = { type FormValues = {
siteLogo?: string siteLogo?: string
@@ -12,25 +19,45 @@ type FormValues = {
footerDescription: string footerDescription: string
shopPostalCode?: string shopPostalCode?: string
downloadAppDetails: { id: string; pic: string; url: string }[] downloadAppDetails: { id: string; pic: string; url: string }[]
socialMediaLinks: { icon: string; url: string }[] socialMediaLinks: SocialMediaLink[]
} }
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> = ({ formik }) => { const SocialMediaSection: FC<Props> = ({
formik,
setPendingFiles,
uploadingIndexes,
}) => {
const addSocialMediaLink = () => { const addSocialMediaLink = () => {
const currentLinks = formik.values.socialMediaLinks || [] const currentLinks = formik.values.socialMediaLinks || []
formik.setFieldValue('socialMediaLinks', [...currentLinks, { icon: '', url: '' }]) const newId = Date.now().toString()
formik.setFieldValue('socialMediaLinks', [...currentLinks, { id: newId, icon: '', url: '' }])
} }
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: 'icon' | 'url', value: string) => { const updateSocialMediaLink = (index: number, field: 'url', value: string) => {
const currentLinks = formik.values.socialMediaLinks || [] const currentLinks = formik.values.socialMediaLinks || []
const updatedLinks = currentLinks.map((item, i) => const updatedLinks = currentLinks.map((item, i) =>
i === index ? { ...item, [field]: value } : item i === index ? { ...item, [field]: value } : item
@@ -38,6 +65,30 @@ const SocialMediaSection: FC<Props> = ({ formik }) => {
formik.setFieldValue('socialMediaLinks', updatedLinks) formik.setFieldValue('socialMediaLinks', updatedLinks)
} }
const handleSocialMediaIconUpload = (index: number, files: File[]) => {
if (files.length === 0) return
const item = formik.values.socialMediaLinks?.[index]
if (!item) return
// ذخیره فایل برای آپلود بعدی
setPendingFiles(prev => ({
...prev,
[item.id]: files[0]
}))
// نمایش preview
updateSocialMediaLinkIcon(index, URL.createObjectURL(files[0]))
}
const updateSocialMediaLinkIcon = (index: number, iconUrl: string) => {
const currentLinks = formik.values.socialMediaLinks || []
const updatedLinks = currentLinks.map((item, i) =>
i === index ? { ...item, icon: iconUrl } : item
)
formik.setFieldValue('socialMediaLinks', updatedLinks)
}
return ( return (
<div className='mt-6 bg-white py-8 xl:px-10 px-6 rounded-3xl'> <div className='mt-6 bg-white py-8 xl:px-10 px-6 rounded-3xl'>
<div className='flex justify-between items-center mb-6'> <div className='flex justify-between items-center mb-6'>
@@ -63,12 +114,21 @@ const SocialMediaSection: FC<Props> = ({ formik }) => {
</Button> </Button>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<Input <div>
label="آیکون" <label className="block text-sm font-medium mb-2">آیکون</label>
value={item.icon} <UploadBox
onChange={(e) => updateSocialMediaLink(index, 'icon', e.target.value)} label=""
placeholder="مثال: telegram" isMultiple={false}
onChange={(files) => handleSocialMediaIconUpload(index, files)}
isReset={false}
/> />
{item.icon && (
<img src={item.icon} alt={`آیکون ${index + 1}`} className="mt-2 w-10 h-10 object-contain rounded" />
)}
{uploadingIndexes.has(index) && (
<div className="mt-2 text-sm text-blue-600">در حال آپلود...</div>
)}
</div>
<Input <Input
label="لینک" label="لینک"
value={item.url} value={item.url}