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 ( +
+

اطلاعات اصلی

+ +
+
+ + +
+ +