diff --git a/src/config/Pages.ts b/src/config/Pages.ts
index d5a5042..2dd09a3 100644
--- a/src/config/Pages.ts
+++ b/src/config/Pages.ts
@@ -68,6 +68,7 @@ export const Pages = {
setting: {
shop: "/settings/shop",
banners: "/settings/banners",
+ siteSetting: "/settings/site-setting",
},
wallet: "/wallet",
profile: "/profile",
diff --git a/src/pages/setting/Banners.tsx b/src/pages/setting/Banners.tsx
index 1ed5b9d..bc49947 100644
--- a/src/pages/setting/Banners.tsx
+++ b/src/pages/setting/Banners.tsx
@@ -64,7 +64,7 @@ const Banners: FC = () => {
-
مدیریت بنرها
+ مدیریت بنرها
-
-
- {banner.order}
-
- |
+
|
handleStatusToggle(banner)}
isLoading={updateBanner.isPending}
- label={banner.isActive ? 'فعال' : 'غیرفعال'}
/>
|
diff --git a/src/pages/setting/SiteSetting.tsx b/src/pages/setting/SiteSetting.tsx
index fdbe0bc..b53332d 100644
--- a/src/pages/setting/SiteSetting.tsx
+++ b/src/pages/setting/SiteSetting.tsx
@@ -1,8 +1,199 @@
-import { type FC } from 'react'
+import { type FC, useEffect, useState } from 'react'
+import { useFormik } from 'formik'
+import * as Yup from 'yup'
+import { useGetSiteSetting, useUpdateSiteSetting, useGetShop } from './hooks/useSettingData'
+import { uploadSingle } from '../uploader/service/UploaderService'
+
+type DownloadAppDetail = {
+ id: string
+ pic: string
+ url: string
+}
+
+type FormValues = {
+ siteLogo?: string
+ footerPhone: string
+ footerEmail: string
+ footerAddress: string
+ footerDescription: string
+ shopPostalCode?: string
+ downloadAppDetails: DownloadAppDetail[]
+ socialMediaLinks: { icon: string; url: string }[]
+}
+import Button from '../../components/Button'
+import { TickCircle } from 'iconsax-react'
+import MainInfoSection from './components/MainInfoSection'
+import FilesSection from './components/FilesSection'
+import DownloadAppSection from './components/DownloadAppSection'
+import SocialMediaSection from './components/SocialMediaSection'
+import { toast } from 'react-toastify'
+import { extractErrorMessage } from '../../helpers/utils'
+
+const validationSchema = Yup.object().shape({
+ footerPhone: Yup.string().trim().required('تلفن فوتر الزامی است'),
+ footerEmail: Yup.string().email('ایمیل معتبر نیست').required('ایمیل فوتر الزامی است'),
+ footerAddress: Yup.string().trim().required('آدرس فوتر الزامی است'),
+ footerDescription: Yup.string().trim().required('توضیحات فوتر الزامی است'),
+ shopPostalCode: Yup.string().trim(),
+ siteLogo: Yup.string(),
+ downloadAppDetails: Yup.array(),
+ socialMediaLinks: Yup.array()
+})
const SiteSetting: FC = () => {
+ const { data, isLoading } = useGetSiteSetting()
+ const { data: shopData } = useGetShop()
+ const updateSiteSetting = useUpdateSiteSetting()
+ const [isDataLoaded, setIsDataLoaded] = useState(false)
+ const [uploadingIndexes, setUploadingIndexes] = useState>(new Set())
+ const [pendingFiles, setPendingFiles] = useState<{ [key: string]: File }>({})
+
+ const formik = useFormik({
+ initialValues: {
+ siteLogo: undefined,
+ footerPhone: '',
+ footerEmail: '',
+ footerAddress: '',
+ footerDescription: '',
+ shopPostalCode: '',
+ downloadAppDetails: [],
+ socialMediaLinks: []
+ },
+ validationSchema,
+ validateOnChange: false,
+ validateOnBlur: false,
+ onSubmit: async (values) => {
+ // اول همه فایلهای pending رو آپلود کن
+ if (Object.keys(pendingFiles).length > 0) {
+ await uploadAllPendingFiles()
+ // پاک کردن pending files بعد از آپلود موفق
+ setPendingFiles({})
+ }
+
+ // transform data برای ارسال به API (حذف id)
+ const transformedValues = {
+ ...values,
+ downloadAppDetails: values.downloadAppDetails.map((item) => ({
+ pic: item.pic,
+ url: item.url
+ }))
+ }
+
+ // حالا فرم رو submit کن
+ updateSiteSetting.mutate(transformedValues, {
+ onSuccess: () => {
+ toast.success('تنظیمات سایت با موفقیت بروزرسانی شد')
+ // پاک کردن pending files بعد از submit موفق
+ setPendingFiles({})
+ },
+ onError: (error) => {
+ toast.error(extractErrorMessage(error))
+ }
+ })
+ }
+ })
+
+ useEffect(() => {
+ if (data?.results?.siteSetting && shopData?.results?.shop) {
+ formik.setValues({
+ siteLogo: data.results.siteSetting.siteLogo || undefined,
+ footerPhone: data.results.siteSetting.footerPhone || '',
+ footerEmail: data.results.siteSetting.footerEmail || '',
+ footerAddress: data.results.siteSetting.footerAddress || '',
+ footerDescription: data.results.siteSetting.footerDescription || '',
+ shopPostalCode: shopData.results.shop.shopPostalCode || '',
+ downloadAppDetails: (data.results.siteSetting.downloadAppDetails || []).map((item: { _id?: string; pic: string; url: string }, index: number) => ({
+ id: item._id || `existing-${index}-${Date.now()}`,
+ pic: item.pic,
+ url: item.url
+ })),
+ socialMediaLinks: data.results.siteSetting.socialMediaLinks || []
+ }, false)
+ setIsDataLoaded(true)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [data, shopData])
+
+
+ const uploadAllPendingFiles = async (): Promise<{ [key: string]: string }> => {
+ const uploadedUrls: { [key: string]: string } = {}
+
+ for (const [id, file] of Object.entries(pendingFiles)) {
+ // پیدا کردن index آیتم بر اساس id
+ const index = formik.values.downloadAppDetails?.findIndex(item => item.id === id)
+ if (index === undefined || index === -1) continue
+
+ try {
+ setUploadingIndexes(prev => new Set(prev).add(index))
+
+ const uploadResponse = await uploadSingle(file)
+ const imageUrl = uploadResponse.results.url
+
+ uploadedUrls[id] = imageUrl
+
+ // بروزرسانی formik
+ const currentDetails = formik.values.downloadAppDetails || []
+ const updatedDetails = currentDetails.map((item, i) =>
+ i === index ? { ...item, pic: imageUrl } : item
+ )
+ formik.setFieldValue('downloadAppDetails', updatedDetails)
+
+ } catch (error) {
+ toast.error(`خطا در آپلود تصویر آیتم ${index + 1}`)
+ throw error
+ } finally {
+ setUploadingIndexes(prev => {
+ const newSet = new Set(prev)
+ newSet.delete(index)
+ return newSet
+ })
+ }
+ }
+
+ return uploadedUrls
+ }
+
+
+ if (isLoading) {
+ return در حال بارگذاری...
+ }
+
return (
- SiteSetting
+
+
+
+ تنظیمات سایت
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/src/pages/setting/components/DownloadAppSection.tsx b/src/pages/setting/components/DownloadAppSection.tsx
new file mode 100644
index 0000000..56d7d9f
--- /dev/null
+++ b/src/pages/setting/components/DownloadAppSection.tsx
@@ -0,0 +1,140 @@
+import { type FC } from 'react'
+import type { FormikProps } from 'formik'
+import { Add, Trash } from 'iconsax-react'
+import Button from '../../../components/Button'
+import Input from '../../../components/Input'
+import UploadBox from '../../../components/UploadBox'
+
+type DownloadAppDetail = {
+ id: string
+ pic: string
+ url: string
+}
+
+type FormValues = {
+ siteLogo?: string
+ footerPhone: string
+ footerEmail: string
+ footerAddress: string
+ footerDescription: string
+ shopPostalCode?: string
+ downloadAppDetails: DownloadAppDetail[]
+ socialMediaLinks: { icon: string; url: string }[]
+}
+
+type Props = {
+ formik: FormikProps
+ pendingFiles: { [key: string]: File }
+ setPendingFiles: React.Dispatch>
+ uploadingIndexes: Set
+ setUploadingIndexes: React.Dispatch>>
+}
+
+const DownloadAppSection: FC = ({
+ formik,
+ pendingFiles,
+ setPendingFiles,
+ uploadingIndexes,
+ setUploadingIndexes
+}) => {
+ const addDownloadAppDetail = () => {
+ const currentDetails = formik.values.downloadAppDetails || []
+ const newId = Date.now().toString()
+ formik.setFieldValue('downloadAppDetails', [...currentDetails, { id: newId, pic: '', url: '' }])
+ }
+
+ const removeDownloadAppDetail = (index: number) => {
+ const currentDetails = formik.values.downloadAppDetails || []
+ const itemToRemove = currentDetails[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 currentDetails = formik.values.downloadAppDetails || []
+ const updatedDetails = currentDetails.map((item, i) =>
+ i === index ? { ...item, [field]: value } : item
+ )
+ formik.setFieldValue('downloadAppDetails', updatedDetails)
+ }
+
+ const handleDownloadAppImageUpload = (index: number, files: File[]) => {
+ if (files.length === 0) return
+
+ const item = formik.values.downloadAppDetails?.[index]
+ if (!item) return
+
+ // ذخیره فایل برای آپلود بعدی
+ setPendingFiles(prev => ({
+ ...prev,
+ [item.id]: files[0]
+ }))
+
+ // نمایش preview
+ updateDownloadAppDetail(index, 'pic', URL.createObjectURL(files[0]))
+ }
+
+ return (
+
+
+ جزئیات دانلود اپ
+
+
+
+
+ {formik.values.downloadAppDetails?.map((item, index) => (
+
+
+ آیتم {index + 1}
+
+
+
+
+
+ handleDownloadAppImageUpload(index, files)}
+ isReset={false}
+ />
+ {item.pic && (
+
+ )}
+ {uploadingIndexes.has(index) && (
+ در حال آپلود...
+ )}
+
+ updateDownloadAppDetail(index, 'url', e.target.value)}
+ placeholder="URL دانلود"
+ />
+
+
+ ))}
+
+
+ )
+}
+
+export default DownloadAppSection
diff --git a/src/pages/setting/components/FilesSection.tsx b/src/pages/setting/components/FilesSection.tsx
new file mode 100644
index 0000000..f46f042
--- /dev/null
+++ b/src/pages/setting/components/FilesSection.tsx
@@ -0,0 +1,64 @@
+import { type FC } from 'react'
+import type { FormikProps } from 'formik'
+import UploadBox from '../../../components/UploadBox'
+
+type FormValues = {
+ siteLogo?: string
+ footerPhone: string
+ footerEmail: string
+ footerAddress: string
+ footerDescription: string
+ shopPostalCode?: string
+ downloadAppDetails: { id: string; pic: string; url: string }[]
+ socialMediaLinks: { icon: string; url: string }[]
+}
+
+type Props = {
+ formik: FormikProps
+}
+
+const FilesSection: FC = ({ formik }) => {
+ return (
+
+
+ فایلها
+
+
+
+
+ {
+ if (files.length > 0) {
+ formik.setFieldValue('siteLogo', URL.createObjectURL(files[0]))
+ }
+ }}
+ />
+ {formik.values.siteLogo && (
+
+ )}
+
+
+
+
+ {
+ if (files.length > 0) {
+ formik.setFieldValue('shopPostalCode', URL.createObjectURL(files[0]))
+ }
+ }}
+ />
+ {formik.values.shopPostalCode && (
+
+ )}
+
+
+
+
+ )
+}
+
+export default FilesSection
diff --git a/src/pages/setting/components/MainInfoSection.tsx b/src/pages/setting/components/MainInfoSection.tsx
new file mode 100644
index 0000000..ab5fb15
--- /dev/null
+++ b/src/pages/setting/components/MainInfoSection.tsx
@@ -0,0 +1,64 @@
+import { type FC } from 'react'
+import type { FormikProps } from 'formik'
+import Input from '../../../components/Input'
+import Textarea from '../../../components/Textarea'
+import TextEditor from '../../../components/TextEditor'
+
+type FormValues = {
+ siteLogo?: string
+ footerPhone: string
+ footerEmail: string
+ footerAddress: string
+ footerDescription: string
+ shopPostalCode?: string
+ downloadAppDetails: { id: string; pic: string; url: string }[]
+ socialMediaLinks: { icon: string; url: string }[]
+}
+
+type Props = {
+ formik: FormikProps
+ isDataLoaded: boolean
+}
+
+const MainInfoSection: FC = ({ formik, isDataLoaded }) => {
+ return (
+
+ اطلاعات اصلی
+
+
+
+
+
+
+
+
+
+
+
+ formik.setFieldValue('footerDescription', value)}
+ height="h-32"
+ />
+
+
+
+ )
+}
+
+export default MainInfoSection
diff --git a/src/pages/setting/components/SocialMediaSection.tsx b/src/pages/setting/components/SocialMediaSection.tsx
new file mode 100644
index 0000000..59e4a80
--- /dev/null
+++ b/src/pages/setting/components/SocialMediaSection.tsx
@@ -0,0 +1,86 @@
+import { type FC } from 'react'
+import type { FormikProps } from 'formik'
+import { Add, Trash } from 'iconsax-react'
+import Button from '../../../components/Button'
+import Input from '../../../components/Input'
+
+type FormValues = {
+ siteLogo?: string
+ footerPhone: string
+ footerEmail: string
+ footerAddress: string
+ footerDescription: string
+ shopPostalCode?: string
+ downloadAppDetails: { id: string; pic: string; url: string }[]
+ socialMediaLinks: { icon: string; url: string }[]
+}
+
+type Props = {
+ formik: FormikProps
+}
+
+const SocialMediaSection: FC = ({ formik }) => {
+ const addSocialMediaLink = () => {
+ const currentLinks = formik.values.socialMediaLinks || []
+ formik.setFieldValue('socialMediaLinks', [...currentLinks, { icon: '', url: '' }])
+ }
+
+ const removeSocialMediaLink = (index: number) => {
+ const currentLinks = formik.values.socialMediaLinks || []
+ formik.setFieldValue('socialMediaLinks', currentLinks.filter((_, i) => i !== index))
+ }
+
+ const updateSocialMediaLink = (index: number, field: 'icon' | 'url', value: string) => {
+ const currentLinks = formik.values.socialMediaLinks || []
+ const updatedLinks = currentLinks.map((item, i) =>
+ i === index ? { ...item, [field]: value } : item
+ )
+ formik.setFieldValue('socialMediaLinks', updatedLinks)
+ }
+
+ return (
+
+
+ لینکهای رسانههای اجتماعی
+
+
+
+
+ {formik.values.socialMediaLinks?.map((item, index) => (
+
+ ))}
+
+
+ )
+}
+
+export default SocialMediaSection
diff --git a/src/pages/setting/hooks/useSettingData.ts b/src/pages/setting/hooks/useSettingData.ts
index 02dead9..b34b3c5 100644
--- a/src/pages/setting/hooks/useSettingData.ts
+++ b/src/pages/setting/hooks/useSettingData.ts
@@ -59,11 +59,11 @@ export const useUpdateFaq = () => {
});
};
-export const useGetSiteSetting = (page: SiteSettingPageEnum) => {
+export const useGetSiteSetting = (page?: SiteSettingPageEnum) => {
return useQuery({
- queryKey: ["siteSetting", page],
+ queryKey: page ? ["siteSetting", page] : ["siteSetting"],
queryFn: () => api.getSiteSetting(page),
- enabled: !!page,
+ enabled: true,
});
};
diff --git a/src/pages/setting/service/SettingService.ts b/src/pages/setting/service/SettingService.ts
index d38c370..de82f18 100644
--- a/src/pages/setting/service/SettingService.ts
+++ b/src/pages/setting/service/SettingService.ts
@@ -61,10 +61,11 @@ export const updateFaq = async (id: string, params: CreateFaqType) => {
};
export const getSiteSetting = async (
- page: SiteSettingPageEnum
+ page?: SiteSettingPageEnum
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise => {
- const { data } = await axios.get(`/site-setting?name=${page}`);
+ const url = page ? `/site-setting?name=${page}` : `/site-setting`;
+ const { data } = await axios.get(url);
return data;
};
diff --git a/src/pages/setting/types/Types.ts b/src/pages/setting/types/Types.ts
index ea590ea..41466fa 100644
--- a/src/pages/setting/types/Types.ts
+++ b/src/pages/setting/types/Types.ts
@@ -75,11 +75,16 @@ export interface SiteSetting {
downloadAppDetails?: {
pic: string;
url: string;
+ _id?: string;
+ id?: string;
}[];
socialMediaLinks?: {
icon: string;
url: string;
+ _id?: string;
+ id?: string;
}[];
+ shopPostalCode?: string;
}
export interface SiteSettingResults {
@@ -106,11 +111,16 @@ export interface UpdateSiteSetting {
downloadAppDetails?: {
pic: string;
url: string;
+ _id?: string;
+ id?: string;
}[];
socialMediaLinks?: {
icon: string;
url: string;
+ _id?: string;
+ id?: string;
}[];
+ shopPostalCode?: string;
}
export interface CreateDocumentType {
diff --git a/src/router/Main.tsx b/src/router/Main.tsx
index 87a73b9..ac150df 100644
--- a/src/router/Main.tsx
+++ b/src/router/Main.tsx
@@ -57,6 +57,7 @@ import ManageDocument from '@/pages/setting/ManageDocument'
import Pricing from '@/pages/setting/Pricing'
import Shop from '@/pages/setting/Shop'
import Banners from '@/pages/setting/Banners'
+import SiteSetting from '@/pages/setting/SiteSetting'
const MainRouter: FC = () => {
@@ -139,6 +140,7 @@ const MainRouter: FC = () => {
} />
} />
} />
+ } />
diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx
index 3efc36a..2bff307 100644
--- a/src/shared/SideBar.tsx
+++ b/src/shared/SideBar.tsx
@@ -766,8 +766,8 @@ const SettingsSubMenu: FC = () => {
/>
|