site setting
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user