setting footer
This commit is contained in:
@@ -68,6 +68,7 @@ export const Pages = {
|
|||||||
setting: {
|
setting: {
|
||||||
shop: "/settings/shop",
|
shop: "/settings/shop",
|
||||||
banners: "/settings/banners",
|
banners: "/settings/banners",
|
||||||
|
siteSetting: "/settings/site-setting",
|
||||||
},
|
},
|
||||||
wallet: "/wallet",
|
wallet: "/wallet",
|
||||||
profile: "/profile",
|
profile: "/profile",
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const Banners: FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center gap-4 mb-5">
|
<div className="flex justify-between items-center gap-4 mb-5">
|
||||||
<div className='flex items-center gap-3'>
|
<div className='flex items-center gap-3'>
|
||||||
<h1 className='text-xl font-semibold'>مدیریت بنرها</h1>
|
<h1>مدیریت بنرها</h1>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setIsCreateModalOpen(true)}
|
onClick={() => setIsCreateModalOpen(true)}
|
||||||
@@ -139,17 +139,12 @@ const Banners: FC = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
<Td text="">
|
<Td text={banner.order.toString()} />
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="font-medium text-lg">{banner.order}</span>
|
|
||||||
</div>
|
|
||||||
</Td>
|
|
||||||
<Td text="">
|
<Td text="">
|
||||||
<Switch
|
<Switch
|
||||||
active={banner.isActive}
|
active={banner.isActive}
|
||||||
onChange={() => handleStatusToggle(banner)}
|
onChange={() => handleStatusToggle(banner)}
|
||||||
isLoading={updateBanner.isPending}
|
isLoading={updateBanner.isPending}
|
||||||
label={banner.isActive ? 'فعال' : 'غیرفعال'}
|
|
||||||
/>
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
<Td text="">
|
<Td text="">
|
||||||
|
|||||||
@@ -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 SiteSetting: FC = () => {
|
||||||
|
const { data, isLoading } = useGetSiteSetting()
|
||||||
|
const { data: shopData } = useGetShop()
|
||||||
|
const updateSiteSetting = useUpdateSiteSetting()
|
||||||
|
const [isDataLoaded, setIsDataLoaded] = useState(false)
|
||||||
|
const [uploadingIndexes, setUploadingIndexes] = useState<Set<number>>(new Set())
|
||||||
|
const [pendingFiles, setPendingFiles] = useState<{ [key: string]: File }>({})
|
||||||
|
|
||||||
|
const formik = useFormik<FormValues>({
|
||||||
|
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 <div className="flex justify-center items-center h-64">در حال بارگذاری...</div>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>SiteSetting</div>
|
<div className='mt-4'>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<div className='flex items-center gap-3'>
|
||||||
|
<h1>تنظیمات سایت</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
className='px-6'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={updateSiteSetting.isPending}
|
||||||
|
disabled={(!isDataLoaded || !formik.isValid) || updateSiteSetting.isPending}
|
||||||
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<TickCircle size={16} color='#fff' />
|
||||||
|
<div>بروزرسانی تنظیمات</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-6 xl:mt-8 mt-6'>
|
||||||
|
<MainInfoSection formik={formik} isDataLoaded={isDataLoaded} />
|
||||||
|
<FilesSection formik={formik} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DownloadAppSection
|
||||||
|
formik={formik}
|
||||||
|
pendingFiles={pendingFiles}
|
||||||
|
setPendingFiles={setPendingFiles}
|
||||||
|
uploadingIndexes={uploadingIndexes}
|
||||||
|
setUploadingIndexes={setUploadingIndexes}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SocialMediaSection formik={formik} />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<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> = ({
|
||||||
|
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 (
|
||||||
|
<div className='mt-6 bg-white py-8 xl:px-10 px-6 rounded-3xl'>
|
||||||
|
<div className='flex justify-between items-center mb-6'>
|
||||||
|
<h2 className='text-lg font-medium'>جزئیات دانلود اپ</h2>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={addDownloadAppDetail}
|
||||||
|
className="flex items-center gap-2 w-fit"
|
||||||
|
>
|
||||||
|
<Add size={16} color='#6B7280' />
|
||||||
|
افزودن
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{formik.values.downloadAppDetails?.map((item, index) => (
|
||||||
|
<div key={index} className="border border-gray-200 rounded-lg p-4">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<span className="text-sm font-medium">آیتم {index + 1}</span>
|
||||||
|
<Button className='w-fit' size="sm" variant="danger" onClick={() => removeDownloadAppDetail(index)}>
|
||||||
|
<Trash size={16} color='white' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-2">تصویر</label>
|
||||||
|
<UploadBox
|
||||||
|
label=""
|
||||||
|
isMultiple={false}
|
||||||
|
onChange={(files) => handleDownloadAppImageUpload(index, files)}
|
||||||
|
isReset={false}
|
||||||
|
/>
|
||||||
|
{item.pic && (
|
||||||
|
<img src={item.pic} alt={`تصویر ${index + 1}`} className="mt-2 w-20 h-20 object-contain rounded" />
|
||||||
|
)}
|
||||||
|
{uploadingIndexes.has(index) && (
|
||||||
|
<div className="mt-2 text-sm text-blue-600">در حال آپلود...</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
label="لینک"
|
||||||
|
value={item.url}
|
||||||
|
onChange={(e) => updateDownloadAppDetail(index, 'url', e.target.value)}
|
||||||
|
placeholder="URL دانلود"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DownloadAppSection
|
||||||
@@ -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<FormValues>
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilesSection: FC<Props> = ({ formik }) => {
|
||||||
|
return (
|
||||||
|
<div className='w-80 space-y-6'>
|
||||||
|
<div className='text-sm bg-white py-8 px-6 rounded-3xl'>
|
||||||
|
<h2 className='text-lg font-medium mb-6'>فایلها</h2>
|
||||||
|
|
||||||
|
<div className='space-y-6'>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-2">لوگو سایت</label>
|
||||||
|
<UploadBox
|
||||||
|
label=""
|
||||||
|
isMultiple={false}
|
||||||
|
onChange={(files) => {
|
||||||
|
if (files.length > 0) {
|
||||||
|
formik.setFieldValue('siteLogo', URL.createObjectURL(files[0]))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{formik.values.siteLogo && (
|
||||||
|
<img src={formik.values.siteLogo} alt="لوگو سایت" className="mt-2 w-20 h-20 object-contain rounded" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-2">کد پستی فروشگاه</label>
|
||||||
|
<UploadBox
|
||||||
|
label=""
|
||||||
|
isMultiple={false}
|
||||||
|
onChange={(files) => {
|
||||||
|
if (files.length > 0) {
|
||||||
|
formik.setFieldValue('shopPostalCode', URL.createObjectURL(files[0]))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{formik.values.shopPostalCode && (
|
||||||
|
<img src={formik.values.shopPostalCode} alt="کد پستی فروشگاه" className="mt-2 w-20 h-20 object-contain rounded" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilesSection
|
||||||
@@ -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<FormValues>
|
||||||
|
isDataLoaded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const MainInfoSection: FC<Props> = ({ formik, isDataLoaded }) => {
|
||||||
|
return (
|
||||||
|
<div className='flex-1 text-sm bg-white py-8 xl:px-10 px-6 rounded-3xl'>
|
||||||
|
<h2 className='text-lg font-medium mb-6'>اطلاعات اصلی</h2>
|
||||||
|
|
||||||
|
<div className='space-y-6'>
|
||||||
|
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
|
||||||
|
<Input
|
||||||
|
label='تلفن فوتر'
|
||||||
|
placeholder='مثال: ۵۷۲۷ - ۹۴۵ - ۰۹۳۹'
|
||||||
|
{...formik.getFieldProps('footerPhone')}
|
||||||
|
error_text={isDataLoaded && formik.touched.footerPhone && formik.errors.footerPhone ? formik.errors.footerPhone : ''}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label='ایمیل فوتر'
|
||||||
|
placeholder='مثال: info@shinan.ir'
|
||||||
|
{...formik.getFieldProps('footerEmail')}
|
||||||
|
error_text={isDataLoaded && formik.touched.footerEmail && formik.errors.footerEmail ? formik.errors.footerEmail : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
label='آدرس فوتر'
|
||||||
|
placeholder='آدرس کامل را وارد کنید...'
|
||||||
|
{...formik.getFieldProps('footerAddress')}
|
||||||
|
error_text={isDataLoaded && formik.touched.footerAddress && formik.errors.footerAddress ? formik.errors.footerAddress : ''}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-2">توضیحات فوتر</label>
|
||||||
|
<TextEditor
|
||||||
|
value={formik.values.footerDescription || ''}
|
||||||
|
onChange={(value) => formik.setFieldValue('footerDescription', value)}
|
||||||
|
height="h-32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainInfoSection
|
||||||
@@ -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<FormValues>
|
||||||
|
}
|
||||||
|
|
||||||
|
const SocialMediaSection: FC<Props> = ({ 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 (
|
||||||
|
<div className='mt-6 bg-white py-8 xl:px-10 px-6 rounded-3xl'>
|
||||||
|
<div className='flex justify-between items-center mb-6'>
|
||||||
|
<h2 className='text-lg font-medium'>لینکهای رسانههای اجتماعی</h2>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={addSocialMediaLink}
|
||||||
|
className="flex items-center gap-2 w-fit"
|
||||||
|
>
|
||||||
|
<Add size={16} color='#6B7280' />
|
||||||
|
افزودن
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{formik.values.socialMediaLinks?.map((item, index) => (
|
||||||
|
<div key={index} className="border border-gray-200 rounded-lg p-4">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<span className="text-sm font-medium">لینک {index + 1}</span>
|
||||||
|
<Button className='w-fit' size="sm" variant="danger" onClick={() => removeSocialMediaLink(index)}>
|
||||||
|
<Trash size={16} color='white' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Input
|
||||||
|
label="آیکون"
|
||||||
|
value={item.icon}
|
||||||
|
onChange={(e) => updateSocialMediaLink(index, 'icon', e.target.value)}
|
||||||
|
placeholder="مثال: telegram"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="لینک"
|
||||||
|
value={item.url}
|
||||||
|
onChange={(e) => updateSocialMediaLink(index, 'url', e.target.value)}
|
||||||
|
placeholder="URL صفحه"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SocialMediaSection
|
||||||
@@ -59,11 +59,11 @@ export const useUpdateFaq = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetSiteSetting = (page: SiteSettingPageEnum) => {
|
export const useGetSiteSetting = (page?: SiteSettingPageEnum) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["siteSetting", page],
|
queryKey: page ? ["siteSetting", page] : ["siteSetting"],
|
||||||
queryFn: () => api.getSiteSetting(page),
|
queryFn: () => api.getSiteSetting(page),
|
||||||
enabled: !!page,
|
enabled: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -61,10 +61,11 @@ export const updateFaq = async (id: string, params: CreateFaqType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSiteSetting = async (
|
export const getSiteSetting = async (
|
||||||
page: SiteSettingPageEnum
|
page?: SiteSettingPageEnum
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
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;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -75,11 +75,16 @@ export interface SiteSetting {
|
|||||||
downloadAppDetails?: {
|
downloadAppDetails?: {
|
||||||
pic: string;
|
pic: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
_id?: string;
|
||||||
|
id?: string;
|
||||||
}[];
|
}[];
|
||||||
socialMediaLinks?: {
|
socialMediaLinks?: {
|
||||||
icon: string;
|
icon: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
_id?: string;
|
||||||
|
id?: string;
|
||||||
}[];
|
}[];
|
||||||
|
shopPostalCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SiteSettingResults {
|
export interface SiteSettingResults {
|
||||||
@@ -106,11 +111,16 @@ export interface UpdateSiteSetting {
|
|||||||
downloadAppDetails?: {
|
downloadAppDetails?: {
|
||||||
pic: string;
|
pic: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
_id?: string;
|
||||||
|
id?: string;
|
||||||
}[];
|
}[];
|
||||||
socialMediaLinks?: {
|
socialMediaLinks?: {
|
||||||
icon: string;
|
icon: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
_id?: string;
|
||||||
|
id?: string;
|
||||||
}[];
|
}[];
|
||||||
|
shopPostalCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateDocumentType {
|
export interface CreateDocumentType {
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import ManageDocument from '@/pages/setting/ManageDocument'
|
|||||||
import Pricing from '@/pages/setting/Pricing'
|
import Pricing from '@/pages/setting/Pricing'
|
||||||
import Shop from '@/pages/setting/Shop'
|
import Shop from '@/pages/setting/Shop'
|
||||||
import Banners from '@/pages/setting/Banners'
|
import Banners from '@/pages/setting/Banners'
|
||||||
|
import SiteSetting from '@/pages/setting/SiteSetting'
|
||||||
|
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
@@ -139,6 +140,7 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.pages.incomeTariff} element={<Pricing />} />
|
<Route path={Pages.pages.incomeTariff} element={<Pricing />} />
|
||||||
<Route path={Pages.setting.shop} element={<Shop />} />
|
<Route path={Pages.setting.shop} element={<Shop />} />
|
||||||
<Route path={Pages.setting.banners} element={<Banners />} />
|
<Route path={Pages.setting.banners} element={<Banners />} />
|
||||||
|
<Route path={Pages.setting.siteSetting} element={<SiteSetting />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -766,8 +766,8 @@ const SettingsSubMenu: FC = () => {
|
|||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
title={'تنظیمات فوتر'}
|
title={'تنظیمات فوتر'}
|
||||||
isActive={isActive('footer')}
|
isActive={isActive('site-setting')}
|
||||||
link="/settings/footer"
|
link={Pages.setting.siteSetting}
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
title={'تنظیمات بنرها'}
|
title={'تنظیمات بنرها'}
|
||||||
|
|||||||
Reference in New Issue
Block a user