shipmentpage setting

This commit is contained in:
hamid zarghami
2025-09-30 14:13:23 +03:30
parent 3a5d38fe33
commit 456797f63f
12 changed files with 417 additions and 17 deletions
+52
View File
@@ -0,0 +1,52 @@
/* TextEditor RTL and Placeholder Styles */
[dir="rtl"] .ql-editor {
text-align: right;
direction: rtl;
}
[dir="rtl"] .ql-toolbar {
direction: rtl;
}
[dir="rtl"] .ql-toolbar .ql-picker-label {
text-align: right;
}
[dir="rtl"] .ql-toolbar .ql-formats {
margin-left: 0;
margin-right: 15px;
}
[dir="rtl"] .ql-toolbar .ql-formats:first-child {
margin-right: 0;
}
/* Enhanced placeholder styles for RTL */
.ql-editor.ql-blank::before {
color: #6b7280 !important;
content: var(--placeholder-text, "متن خود را وارد کنید...") !important;
font-style: normal !important;
pointer-events: none !important;
position: absolute !important;
right: 15px !important;
top: 12px !important;
left: auto !important;
font-size: 14px !important;
line-height: 1.5 !important;
}
[dir="rtl"] .ql-editor.ql-blank::before {
right: 15px !important;
left: auto !important;
}
/* Ensure placeholder shows when editor is focused but empty */
.ql-editor.ql-blank.ql-has-focus::before {
display: none;
}
/* Custom height for editor */
.h-64 .ql-editor {
min-height: 160px;
}
+65
View File
@@ -0,0 +1,65 @@
import { type FC } from 'react'
import ReactQuill from 'react-quill-new'
import 'react-quill-new/dist/quill.snow.css'
import './TextEditor.css'
interface TextEditorProps {
value: string
onChange: (value: string) => void
placeholder?: string
className?: string
height?: string
}
const TextEditor: FC<TextEditorProps> = ({
value,
onChange,
placeholder = 'متن خود را وارد کنید...',
className = '',
height = 'h-[200px]'
}) => {
const modules = {
toolbar: [
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'font': [] }],
[{ 'size': [] }],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'indent': '-1' }, { 'indent': '+1' }],
['link', 'image', 'video'],
[{ 'color': [] }, { 'background': [] }],
[{ 'align': [] }],
['clean']
],
clipboard: {
matchVisual: false,
},
}
const formats = [
'header', 'font', 'size',
'bold', 'italic', 'underline', 'strike', 'blockquote',
'list', 'bullet', 'indent',
'link', 'image', 'video',
'color', 'background',
'align'
]
return (
<div
className={`${className} mb-14`}
dir="rtl"
style={{ '--placeholder-text': `"${placeholder}"` } as React.CSSProperties}
>
<ReactQuill
theme="snow"
value={value}
onChange={onChange}
modules={modules}
formats={formats}
className={height}
/>
</div>
)
}
export default TextEditor
+1
View File
@@ -58,6 +58,7 @@ export const Pages = {
faq: "/pages/faq",
faqCreate: "/pages/faq/create",
faqUpdate: "/pages/faq/update/",
shipment: "/pages/shipment",
},
wallet: "/wallet",
profile: "/profile",
+107
View File
@@ -0,0 +1,107 @@
import { type FC, useEffect, useState } from 'react'
import { useGetSiteSetting, useUpdateSiteSetting } from './hooks/useSettingData'
import { SiteSettingPageEnum } from './enum/Enum'
import { useFormik } from 'formik'
import type { UpdateSiteSetting } from './types/Types'
import { toast } from 'react-toastify'
import { Pages } from '@/config/Pages'
import * as Yup from 'yup'
import { useNavigate } from 'react-router-dom'
import Button from '@/components/Button'
import TextEditor from '@/components/TextEditor'
import PageLoading from '@/components/PageLoading'
import Error from '@/components/Error'
import { TickCircle } from 'iconsax-react'
const ShipmentPage: FC = () => {
const navigate = useNavigate()
const { data: siteSetting, isLoading: siteSettingLoading, error: siteSettingError } = useGetSiteSetting(SiteSettingPageEnum.shipmentContent)
const updateSiteSettingMutation = useUpdateSiteSetting()
const [formData, setFormData] = useState<UpdateSiteSetting>({ shipmentContent: '' })
const formik = useFormik<UpdateSiteSetting>({
initialValues: formData,
validationSchema: Yup.object({
shipmentContent: Yup.string().required('روش ارسال الزامی است'),
}),
onSubmit: (values) => {
updateSiteSettingMutation.mutate(values, {
onSuccess: () => {
toast.success('روش ارسال با موفقیت ویرایش شد')
navigate(Pages.pages.shipment)
}
})
},
enableReinitialize: true
})
useEffect(() => {
if (siteSetting?.results?.siteSetting?.shipmentContent) {
const newData = { shipmentContent: siteSetting.results.siteSetting.shipmentContent }
setFormData(newData)
}
}, [siteSetting])
if (siteSettingLoading) {
return (
<div className="flex justify-center items-center min-h-[400px]">
<PageLoading />
</div>
)
}
if (siteSettingError) {
return (
<div className="flex justify-center items-center min-h-[400px]">
<Error errorText="خطا در بارگذاری اطلاعات روش ارسال" />
</div>
)
}
return (
<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={updateSiteSettingMutation.isPending}
disabled={!formik.isValid || updateSiteSettingMutation.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'>
<div className='flex-1'>
<div className='bg-white rounded-lg shadow-sm border p-6'>
<div className='mb-4'>
<div className='mb-4'>
متن روش ارسال
</div>
<TextEditor
value={formik.values.shipmentContent || ''}
onChange={(value) => formik.setFieldValue('shipmentContent', value)}
placeholder='متن روش ارسال را وارد کنید...'
/>
{formik.touched.shipmentContent && formik.errors.shipmentContent && (
<div className='text-red-500 text-sm mt-12'>
{formik.errors.shipmentContent}
</div>
)}
</div>
</div>
</div>
</div>
</div>
)
}
export default ShipmentPage
+15
View File
@@ -7,3 +7,18 @@ export enum FaqPageEnum {
ShipmentProcess = "ShipmentProcess",
Policy = "Policy",
}
export enum SiteSettingPageEnum {
siteLogo = "siteLogo",
footerDescription = "footerDescription",
footerAddress = "footerAddress",
footerEmail = "footerEmail",
footerPhone = "footerPhone",
downloadAppDetails = "downloadAppDetails",
socialMediaLinks = "socialMediaLinks",
shipmentContent = "shipmentContent",
faqContent = "faqContent",
policyContent = "policyContent",
returnRulesContent = "returnRulesContent",
jobsContent = "jobsContent",
}
+15 -1
View File
@@ -1,6 +1,6 @@
import * as api from "../service/SettingService";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { FaqPageEnum } from "../enum/Enum";
import { FaqPageEnum, SiteSettingPageEnum } from "../enum/Enum";
import { type CreateFaqType } from "../types/Types";
export const useGetAboutUs = () => {
@@ -57,4 +57,18 @@ export const useUpdateFaq = () => {
queryClient.invalidateQueries({ queryKey: ["faq", params.page] });
},
});
};
export const useGetSiteSetting = (page: SiteSettingPageEnum) => {
return useQuery({
queryKey: ["siteSetting", page],
queryFn: () => api.getSiteSetting(page),
enabled: !!page,
});
};
export const useUpdateSiteSetting = () => {
return useMutation({
mutationFn: api.updateSiteSetting,
});
};
+15 -1
View File
@@ -5,8 +5,10 @@ import type {
CreateFaqType,
FaqDetailResponse,
FaqResponse,
SiteSettingResponse,
UpdateSiteSetting,
} from "../types/Types";
import { FaqPageEnum } from "../enum/Enum";
import { FaqPageEnum, SiteSettingPageEnum } from "../enum/Enum";
export const getAboutUs = async (): Promise<AboutUsResponse> => {
const { data } = await axios.get("/about-us");
@@ -50,3 +52,15 @@ export const updateFaq = async (id: string, params: CreateFaqType) => {
});
return data;
};
export const getSiteSetting = async (
page: SiteSettingPageEnum
): Promise<SiteSettingResponse> => {
const { data } = await axios.get(`/site-setting?name=${page}`);
return data;
};
export const updateSiteSetting = async (params: UpdateSiteSetting) => {
const { data } = await axios.post(`/admin/site-setting/update`, params);
return data;
};
+53
View File
@@ -59,3 +59,56 @@ export interface FaqDetailResponse {
success: boolean;
results: FaqDetailResults;
}
export interface SiteSetting {
_id: string;
siteLogo?: string;
footerPhone?: string;
footerEmail?: string;
footerAddress?: string;
shipmentContent?: string;
faqContent?: string;
policyContent?: string;
returnRulesContent?: string;
jobsContent?: string;
footerDescription?: string;
downloadAppDetails?: {
pic: string;
url: string;
}[];
socialMediaLinks?: {
icon: string;
url: string;
}[];
}
export interface SiteSettingResults {
siteSetting: SiteSetting;
}
export interface SiteSettingResponse {
status: number;
success: boolean;
results: SiteSettingResults;
}
export interface UpdateSiteSetting {
siteLogo?: string;
footerPhone?: string;
footerEmail?: string;
footerAddress?: string;
shipmentContent?: string;
faqContent?: string;
policyContent?: string;
returnRulesContent?: string;
jobsContent?: string;
footerDescription?: string;
downloadAppDetails?: {
pic: string;
url: string;
}[];
socialMediaLinks?: {
icon: string;
url: string;
}[];
}
+2
View File
@@ -49,6 +49,7 @@ import AboutUsCreate from '@/pages/setting/AboutUsCreate'
import FaqList from '@/pages/setting/FaqList'
import FaqCreate from '@/pages/setting/FaqCreate'
import FaqUpdate from '@/pages/setting/FaqUpdate'
import ShipmentPage from '@/pages/setting/ShipmentPage'
const MainRouter: FC = () => {
@@ -123,6 +124,7 @@ const MainRouter: FC = () => {
<Route path={Pages.pages.faq} element={<FaqList />} />
<Route path={Pages.pages.faqCreate} element={<FaqCreate />} />
<Route path={`${Pages.pages.faqUpdate}:id`} element={<FaqUpdate />} />
<Route path={Pages.pages.shipment} element={<ShipmentPage />} />
</Routes>
</div>
</div>
+3 -3
View File
@@ -806,12 +806,12 @@ const PagesSubMenu: FC = () => {
<SubMenuItem
title={'سوالات متداول'}
isActive={isActive('faq')}
link="/pages/faq"
link={Pages.pages.faq}
/>
<SubMenuItem
title={'صفحه روش های ارسال'}
isActive={isActive('shipping')}
link="/pages/shipping"
isActive={isActive('shipment')}
link={Pages.pages.shipment}
/>
<SubMenuItem
title={'حریم خصوصی'}