support plan
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -54,7 +54,7 @@ const ServiceItem: FC<Props> = (props: Props) => {
|
||||
|
||||
|
||||
<div className='mt-4 flex-1 flex items-end'>
|
||||
<Link target={props.isLinkPanel && !props.isDisabled ? '_blank' : ''} to={props.isDisabled ? '#' : props.isLinkPanel ? item.link : Pages.services.detail + item.id}>
|
||||
<Link target={props.isLinkPanel && !props.isDisabled ? '_blank' : ''} to={props.isDisabled ? '#' : props.isLinkPanel ? item.link : Pages.services.detail + item.slug}>
|
||||
<Button
|
||||
className='h-8 w-fit px-5 text-xs text-black bg-description bg-opacity-20 rounded-xl'
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@ const Tabs: FC<Props> = (props: Props) => {
|
||||
spaceBetween={30}
|
||||
className='px-10 max-w-full w-fit items-center text-description mx-auto backdrop-blur-md border-2 border-white gap-10 flex h-[80px] rounded-[32px] bg-white bg-opacity-45'>
|
||||
{
|
||||
props.items.map((item: Item, index: number) => {
|
||||
props.items?.map((item: Item, index: number) => {
|
||||
return (
|
||||
<SwiperSlide style={SWIPER_SLIDE_STYLE} onClick={() => props.onChange(item.value)} key={item.value} className={clx(
|
||||
'flex flex-col max-w-fit mt-[15px] items-center gap-2 cursor-pointer',
|
||||
|
||||
@@ -36,4 +36,7 @@ export const Pages = {
|
||||
profile: "/profile",
|
||||
discounts: "/discounts",
|
||||
financial: "/financial",
|
||||
support: {
|
||||
index: "/support",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -253,3 +253,6 @@ body {
|
||||
overflow-x: hidden;
|
||||
max-width: 100vw;
|
||||
}
|
||||
.plan:nth-child(even) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
+24
-1
@@ -63,7 +63,8 @@
|
||||
"criticisms": "انتقادات",
|
||||
"learning": "آموزش",
|
||||
"setting": "تنظیمات",
|
||||
"logout": "خروج"
|
||||
"logout": "خروج",
|
||||
"support": "سرویس های پشتیبانی"
|
||||
},
|
||||
"header": {
|
||||
"search": "جستجو",
|
||||
@@ -476,5 +477,27 @@
|
||||
"add_service": "افزودن سرویس",
|
||||
"empty_discount": "تخفیفی یافت نشد.",
|
||||
"my_services": "سرویس های من"
|
||||
},
|
||||
"support": {
|
||||
"title": "سرویس های پشتیبانی",
|
||||
"support_plan": "پلن پشتیبانی",
|
||||
"support_plan_list": "لیست پلن پشتیبانی",
|
||||
"support_plan_create": "ایجاد پلن پشتیبانی",
|
||||
"support_plan_edit": "ویرایش پلن پشتیبانی",
|
||||
"learning_docs": "راهنما و مستندات آموزشی",
|
||||
"ticket_support": "ارسال تیکت",
|
||||
"response_time": "زمان پاسخگویی به تیکت",
|
||||
"phone_support": "پاسخگویی تلفنی",
|
||||
"backup_version": "نسخه بکاپ",
|
||||
"technical_expert_access": "دسترسی به متخصصین فنی",
|
||||
"on_site_support": "پشتیبانی در محل",
|
||||
"on_site_training": "آموزش در محل",
|
||||
"ticket_limit": "ظرفیت ارسال تیکت",
|
||||
"plans": "پلن ها",
|
||||
"plan_name": "نام پلن",
|
||||
"price": "قیمت",
|
||||
"duration": "مدت",
|
||||
"features": "ویژگی ها",
|
||||
"actions": "عملیات"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import PageLoading from '../../../components/PageLoading'
|
||||
import { useGetQuikAccess, useQuikAccess, useRemoveQuikAccess } from '../hooks/useHomeData'
|
||||
import ChangeQuikAccess from './ChangeQuikAccess'
|
||||
import { useHomeStore } from '../store/HomeStore'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { toast } from '../../../components/Toast'
|
||||
|
||||
const BoxNewAccessbility: FC = () => {
|
||||
|
||||
@@ -25,12 +27,18 @@ const BoxNewAccessbility: FC = () => {
|
||||
quikAccess.mutate(id, {
|
||||
onSuccess: () => {
|
||||
getQuikAccess.refetch()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
removeQuikAceess.mutate(id, {
|
||||
onSuccess: () => {
|
||||
getQuikAccess.refetch()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { toast } from '../../components/Toast';
|
||||
import { useGetDashboardSummary } from '../home/hooks/useHomeData';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Pages } from '../../config/Pages';
|
||||
|
||||
import { ErrorType } from '../../helpers/types';
|
||||
const Notifications: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation('global');
|
||||
@@ -33,6 +33,9 @@ const Notifications: FC = () => {
|
||||
getDashboard.refetch()
|
||||
refetch()
|
||||
toast(t('success'), 'success')
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import Rate from 'rc-rate'
|
||||
import ServiceImages from './components/ServiceImages'
|
||||
import AvatarImage from '../../assets/images/avatar_image.png'
|
||||
import ServiceHeader from './components/ServiceHeader'
|
||||
import { useGetDetailService } from './hooks/useServiceData'
|
||||
import { useGetServiceBySlug } from './hooks/useServiceData'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import moment from 'moment-jalaali'
|
||||
@@ -19,7 +19,7 @@ const DetailService: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
const getAdsLeft = useGetAds(AdsDisplayLocation.SINGLE_SERVICE_PAGE)
|
||||
|
||||
const getDetailService = useGetDetailService(id ? id : '')
|
||||
const getDetailService = useGetServiceBySlug(id ? id : '')
|
||||
|
||||
return (
|
||||
<div className='w-full flex gap-6 mt-4'>
|
||||
|
||||
@@ -33,7 +33,7 @@ const HeaderBuy: FC<Props> = ({ data, planId }) => {
|
||||
<div>
|
||||
<div className='p-6 bg-white rounded-3xl'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div onClick={() => navigate(Pages.services.detail + data.id)} className='flex cursor-pointer gap-1.5'>
|
||||
<div onClick={() => navigate(Pages.services.detail + data.slug)} className='flex cursor-pointer gap-1.5'>
|
||||
<ArrowRight className='size-4' color='#888888' />
|
||||
<div className='text-xs text-description'>
|
||||
{t('service.back')}
|
||||
|
||||
@@ -49,3 +49,10 @@ export const useCreateReview = () => {
|
||||
mutationFn: (variables: CreateReviewType) => api.createReview(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetServiceBySlug = (slug: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["service-by-slug", slug],
|
||||
queryFn: () => api.getServiceBySlug(slug),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -42,3 +42,8 @@ export const createReview = async (params: CreateReviewType) => {
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getServiceBySlug = async (slug: string) => {
|
||||
const { data } = await axios.get(`/danak-services/slug/${slug}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ export type ItemServiceType = {
|
||||
updatedAt: string;
|
||||
userCount: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type CategoryItemType = {
|
||||
@@ -46,6 +47,7 @@ export type ServiceDetailDataType = {
|
||||
updatedAt: string;
|
||||
userCount: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type PlanItemType = {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { FC, useState } from 'react'
|
||||
import SwitchComponent from '../../../components/Switch'
|
||||
import { useUpdateSetting } from '../hooks/useSettingData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { toast } from '../../../components/Toast'
|
||||
|
||||
type Props = {
|
||||
sms: boolean,
|
||||
@@ -13,7 +15,11 @@ const Sms: FC<Props> = (props: Props) => {
|
||||
|
||||
const handleChange = (value: boolean) => {
|
||||
setSms(value)
|
||||
updateSetting.mutate({ sms: value, id: props.id })
|
||||
updateSetting.mutate({ sms: value, id: props.id }, {
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SidebarHint from './components/SidebarHint'
|
||||
import SupportImage from '../../assets/images/support.png'
|
||||
import Button from '../../components/Button'
|
||||
import { CloseCircle, Document, Element3, TickCircle } from 'iconsax-react'
|
||||
import Tabs from '../../components/Tabs'
|
||||
import { useBuySupportPlan, useGetSupportPlans } from './hooks/useSupportData'
|
||||
import { NumberFormat } from '../../config/func'
|
||||
import { PlanItemType, featureItemType } from './types/SupportTypes'
|
||||
import SupportSkeleton from './components/SupportSkeleton'
|
||||
import SupportIcon from './components/SupportIcon'
|
||||
import { SupportPlanFeatureKey } from './enum/SupportEnum'
|
||||
import { toast } from '../../components/Toast'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Pages } from '../../config/Pages'
|
||||
|
||||
const Support: FC = () => {
|
||||
const { data, isLoading } = useGetSupportPlans()
|
||||
const { t } = useTranslation('global')
|
||||
const [activeTab, setActiveTab] = useState<string>('')
|
||||
const [loadingPlanId, setLoadingPlanId] = useState<string>('')
|
||||
const { mutate: buySupportPlan } = useBuySupportPlan()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data?.supportPlans?.length > 0 && !activeTab) {
|
||||
setActiveTab(data.data.supportPlans[0].id)
|
||||
}
|
||||
}, [data, activeTab])
|
||||
|
||||
const handleBuy = (planId: string) => {
|
||||
setLoadingPlanId(planId)
|
||||
buySupportPlan(planId, {
|
||||
onSuccess: (data) => {
|
||||
toast(data?.data?.message, 'success')
|
||||
navigate(Pages.receipts.detail + data?.data?.invoice?.id)
|
||||
setLoadingPlanId('')
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error?.response?.data?.error.message[0], 'error')
|
||||
setLoadingPlanId('')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (isLoading || !data?.data) {
|
||||
return <SupportSkeleton />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<Helmet>
|
||||
<title>
|
||||
داناک | {t('support.title')}
|
||||
</title>
|
||||
</Helmet>
|
||||
<div>
|
||||
{t('support.title')}
|
||||
</div>
|
||||
|
||||
<div className='xl:hidden mt-5'>
|
||||
<Tabs
|
||||
items={data?.data?.supportPlans?.map((plan: PlanItemType) => ({
|
||||
icon: <Element3 color={activeTab === plan.id ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: plan.name,
|
||||
value: plan.id
|
||||
}))}
|
||||
active={activeTab}
|
||||
onChange={setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-6 xl:mt-8 mt-4'>
|
||||
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||
<div className='xl:flex hidden justify-between mt-10'>
|
||||
<div className='flex-1 flex justify-center'>
|
||||
<img src={SupportImage} alt='support' className='w-[160px]' />
|
||||
</div>
|
||||
|
||||
{
|
||||
data?.data?.supportPlans?.map((item: PlanItemType) => {
|
||||
return (
|
||||
<div key={item.id} className='flex-1 flex flex-col items-center'>
|
||||
<div className='text-center w-full'>
|
||||
<div className='font-bold'>
|
||||
{item.name}
|
||||
</div>
|
||||
<div className='text-sm text-[#595C67] mt-4'>
|
||||
ماهیانه
|
||||
</div>
|
||||
<div className='font-bold text-sm mt-1'>
|
||||
{NumberFormat(item.price)}
|
||||
</div>
|
||||
<div className='mt-1 text-sm'>
|
||||
تومان
|
||||
</div>
|
||||
|
||||
<Button
|
||||
label='ثبت پلن'
|
||||
className='mt-4 mx-auto w-fit px-5'
|
||||
onClick={() => handleBuy(item.id)}
|
||||
isLoading={loadingPlanId === item.id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='xl:flex hidden flex-1 mt-5'>
|
||||
<div className='flex-1 flex flex-col'>
|
||||
{
|
||||
data?.data?.supportPlans?.[0]?.features.map((row: featureItemType) => (
|
||||
<div className='flex h-16 px-4 plan items-center justify-between'>
|
||||
<div className='flex-1 flex items-center gap-2'>
|
||||
<div className='size-8 rounded-2.5 bg-[#EEF0F7] flex items-center justify-center'>
|
||||
<SupportIcon keySupport={row.featureKey as SupportPlanFeatureKey} />
|
||||
</div>
|
||||
<div className='text-xs'>{t(`support.${row.featureKey}`)}</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{
|
||||
data?.data?.supportPlans?.map((item: PlanItemType) => (
|
||||
<div className='flex-1 flex flex-col'>
|
||||
{
|
||||
item.features.map((row) => (
|
||||
<div className='flex h-16 px-4 plan items-center justify-center'>
|
||||
{
|
||||
row.featureType === 'boolean' ?
|
||||
row.featureValue === 'true' ?
|
||||
<TickCircle className='xl:size-8 size-6' color='#01A560' variant='Bold' />
|
||||
:
|
||||
<CloseCircle className='xl:size-8 size-6' color='#D50303' variant='Bold' />
|
||||
:
|
||||
<div className='text-xs'>{row.featureValue}</div>
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Mobile View */}
|
||||
<div className='xl:hidden block mt-5'>
|
||||
{
|
||||
data?.data?.supportPlans?.map((plan: PlanItemType) => (
|
||||
activeTab === plan.id && (
|
||||
<div key={plan.id}>
|
||||
<div className='flex justify-center mb-6'>
|
||||
<img src={SupportImage} alt='support' className='w-[120px]' />
|
||||
</div>
|
||||
<div className='text-center mb-6'>
|
||||
<div className='font-bold text-lg'>{plan.name}</div>
|
||||
<div className='text-sm text-[#595C67] mt-2'>ماهیانه</div>
|
||||
<div className='font-bold text-sm mt-1'>{NumberFormat(plan.price)}</div>
|
||||
<div className='mt-1 text-sm'>تومان</div>
|
||||
<Button
|
||||
label='ثبت پلن'
|
||||
className='mt-4 w-fit px-5 mx-auto'
|
||||
onClick={() => handleBuy(plan.id)}
|
||||
isLoading={loadingPlanId === plan.id}
|
||||
/>
|
||||
</div>
|
||||
<div className='space-y-4'>
|
||||
{
|
||||
plan.features.map((row: featureItemType) => (
|
||||
<div className='flex items-center justify-between p-4 bg-gray-50 rounded-xl'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='size-8 rounded-2.5 bg-[#EEF0F7] flex items-center justify-center'>
|
||||
<Document size={16} color='#595C67' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-xs'>{t(`support.${row.featureKey}`)}</div>
|
||||
</div>
|
||||
{
|
||||
row.featureType === 'boolean' ?
|
||||
row.featureValue === 'true' ?
|
||||
<TickCircle className='size-6' color='#01A560' variant='Bold' />
|
||||
:
|
||||
<CloseCircle className='size-6' color='#D50303' variant='Bold' />
|
||||
:
|
||||
<div className='text-sm'>{row.featureValue}</div>
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SidebarHint />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Support
|
||||
@@ -0,0 +1,67 @@
|
||||
import { TickSquare } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
|
||||
const SidebarHint: FC = () => {
|
||||
return (
|
||||
<div className='bg-white w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
|
||||
<div className='text-sm'>
|
||||
جهت انتخاب بهتر به مسائل زیر توجه بفرمایید:
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<div className='flex items-start gap-2 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-start gap-2 mt-6'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-start gap-2 mt-6'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarHint
|
||||
@@ -0,0 +1,45 @@
|
||||
import { FC } from 'react'
|
||||
import { SupportPlanFeatureKey } from '../enum/SupportEnum'
|
||||
import {
|
||||
Document,
|
||||
MessageQuestion,
|
||||
Clock,
|
||||
Call,
|
||||
Save2,
|
||||
Profile2User,
|
||||
Location,
|
||||
Teacher,
|
||||
Ticket
|
||||
} from 'iconsax-react'
|
||||
|
||||
type Props = {
|
||||
keySupport: SupportPlanFeatureKey
|
||||
}
|
||||
|
||||
const SupportIcon: FC<Props> = ({ keySupport }) => {
|
||||
|
||||
switch (keySupport) {
|
||||
case SupportPlanFeatureKey.LEARNING_DOCS:
|
||||
return <Document size={16} color='#595C67' variant='Bold' />
|
||||
case SupportPlanFeatureKey.TICKET_SUPPORT:
|
||||
return <MessageQuestion size={16} color='#595C67' variant='Bold' />
|
||||
case SupportPlanFeatureKey.RESPONSE_TIME:
|
||||
return <Clock size={16} color='#595C67' variant='Bold' />
|
||||
case SupportPlanFeatureKey.PHONE_SUPPORT:
|
||||
return <Call size={16} color='#595C67' variant='Bold' />
|
||||
case SupportPlanFeatureKey.BACKUP_VERSION:
|
||||
return <Save2 size={16} color='#595C67' variant='Bold' />
|
||||
case SupportPlanFeatureKey.TECHNICAL_EXPERT_ACCESS:
|
||||
return <Profile2User size={16} color='#595C67' variant='Bold' />
|
||||
case SupportPlanFeatureKey.ON_SITE_SUPPORT:
|
||||
return <Location size={16} color='#595C67' variant='Bold' />
|
||||
case SupportPlanFeatureKey.ON_SITE_TRAINING:
|
||||
return <Teacher size={16} color='#595C67' variant='Bold' />
|
||||
case SupportPlanFeatureKey.TICKET_LIMIT:
|
||||
return <Ticket size={16} color='#595C67' variant='Bold' />
|
||||
default:
|
||||
return <Document size={16} color='#595C67' variant='Bold' />
|
||||
}
|
||||
}
|
||||
|
||||
export default SupportIcon
|
||||
@@ -0,0 +1,94 @@
|
||||
import { FC } from 'react'
|
||||
import { Document } from 'iconsax-react'
|
||||
|
||||
const SupportSkeleton: FC = () => {
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex gap-6 xl:mt-8 mt-4'>
|
||||
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||
{/* Desktop View */}
|
||||
<div className='xl:flex hidden justify-between mt-10'>
|
||||
<div className='flex-1 flex justify-center'>
|
||||
<div className='w-[160px] h-[160px] bg-gray-200 rounded-xl animate-pulse' />
|
||||
</div>
|
||||
|
||||
{[1, 2].map((item) => (
|
||||
<div key={item} className='flex-1 flex flex-col items-center'>
|
||||
<div className='text-center w-full'>
|
||||
<div className='h-6 w-32 bg-gray-200 rounded-lg animate-pulse mx-auto' />
|
||||
<div className='h-4 w-20 bg-gray-200 rounded-lg animate-pulse mx-auto mt-4' />
|
||||
<div className='h-5 w-24 bg-gray-200 rounded-lg animate-pulse mx-auto mt-1' />
|
||||
<div className='h-4 w-16 bg-gray-200 rounded-lg animate-pulse mx-auto mt-1' />
|
||||
<div className='h-10 w-28 bg-gray-200 rounded-lg animate-pulse mx-auto mt-4' />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='xl:flex hidden flex-1 mt-5'>
|
||||
<div className='flex-1 flex flex-col'>
|
||||
{[1, 2, 3, 4, 5].map((item) => (
|
||||
<div key={item} className='flex h-16 px-4 plan items-center justify-between'>
|
||||
<div className='flex-1 flex items-center gap-2'>
|
||||
<div className='size-8 rounded-2.5 bg-gray-200 animate-pulse' />
|
||||
<div className='h-4 w-32 bg-gray-200 rounded-lg animate-pulse' />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{[1, 2].map((item) => (
|
||||
<div key={item} className='flex-1 flex flex-col'>
|
||||
{[1, 2, 3, 4, 5].map((feature) => (
|
||||
<div key={feature} className='flex h-16 px-4 plan items-center justify-center'>
|
||||
<div className='size-8 bg-gray-200 rounded-full animate-pulse' />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile View */}
|
||||
<div className='xl:hidden block mt-5'>
|
||||
<div className='flex justify-center mb-6'>
|
||||
<div className='w-[120px] h-[120px] bg-gray-200 rounded-xl animate-pulse' />
|
||||
</div>
|
||||
<div className='text-center mb-6'>
|
||||
<div className='h-6 w-40 bg-gray-200 rounded-lg animate-pulse mx-auto' />
|
||||
<div className='h-4 w-24 bg-gray-200 rounded-lg animate-pulse mx-auto mt-2' />
|
||||
<div className='h-5 w-32 bg-gray-200 rounded-lg animate-pulse mx-auto mt-1' />
|
||||
<div className='h-4 w-20 bg-gray-200 rounded-lg animate-pulse mx-auto mt-1' />
|
||||
<div className='h-10 w-32 bg-gray-200 rounded-lg animate-pulse mx-auto mt-4' />
|
||||
</div>
|
||||
<div className='space-y-4'>
|
||||
{[1, 2, 3, 4, 5].map((item) => (
|
||||
<div key={item} className='flex items-center justify-between p-4 bg-gray-50 rounded-xl'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='size-8 rounded-2.5 bg-gray-200 animate-pulse' />
|
||||
<div className='h-4 w-32 bg-gray-200 rounded-lg animate-pulse' />
|
||||
</div>
|
||||
<div className='size-6 bg-gray-200 rounded-full animate-pulse' />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='xl:block hidden w-[300px]'>
|
||||
<div className='bg-white p-6 rounded-3xl'>
|
||||
<div className='h-6 w-32 bg-gray-200 rounded-lg animate-pulse mb-4' />
|
||||
<div className='space-y-3'>
|
||||
{[1, 2, 3].map((item) => (
|
||||
<div key={item} className='flex items-center gap-2'>
|
||||
<div className='size-6 bg-gray-200 rounded-full animate-pulse' />
|
||||
<div className='h-4 w-40 bg-gray-200 rounded-lg animate-pulse' />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SupportSkeleton
|
||||
@@ -0,0 +1,11 @@
|
||||
export enum SupportPlanFeatureKey {
|
||||
LEARNING_DOCS = "learning_docs", // راهنما و مستندات آموزشی
|
||||
TICKET_SUPPORT = "ticket_support", // ارسال تیکت
|
||||
RESPONSE_TIME = "response_time", // زمان پاسخگویی به تیکت
|
||||
PHONE_SUPPORT = "phone_support", // پاسخگویی تلفنی
|
||||
BACKUP_VERSION = "backup_version", // نسخه بکاپ
|
||||
TECHNICAL_EXPERT_ACCESS = "technical_expert_access", // دسترسی به متخصصین فنی
|
||||
ON_SITE_SUPPORT = "on_site_support", // پشتیبانی در محل
|
||||
ON_SITE_TRAINING = "on_site_training", // آموزش در محل
|
||||
TICKET_LIMIT = "ticket_limit", // ظرفیت ارسال تیکت
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { getSupportPlans, buySupportPlan } from "../service/SupportService";
|
||||
|
||||
export const useGetSupportPlans = () => {
|
||||
return useQuery({ queryKey: ["support-plans"], queryFn: getSupportPlans });
|
||||
};
|
||||
|
||||
export const useBuySupportPlan = () => {
|
||||
return useMutation({
|
||||
mutationFn: (planId: string) => buySupportPlan(planId),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import axios from "../../../config/axios";
|
||||
|
||||
export const getSupportPlans = async () => {
|
||||
const { data } = await axios.get("/support-plans");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const buySupportPlan = async (planId: string) => {
|
||||
const { data } = await axios.post(`/support-plans/${planId}/subscribe`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
export type featureItemType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
featureKey: string;
|
||||
featureValue: string;
|
||||
featureType: string;
|
||||
icon: string | null;
|
||||
};
|
||||
|
||||
export type PlanItemType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
name: string;
|
||||
description: string;
|
||||
price: number;
|
||||
duration: number;
|
||||
isActive: boolean;
|
||||
features: featureItemType[];
|
||||
deletedAt: string | null;
|
||||
};
|
||||
@@ -51,6 +51,9 @@ const CardtoCard: FC = () => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import CallBack from '../pages/payment/CallBack'
|
||||
import MyDiscountList from '../pages/discounts/List'
|
||||
import LearningDetail from '../pages/learning/Detail'
|
||||
import BuyService from '../pages/service/BuyService'
|
||||
import Support from '../pages/support/Support'
|
||||
|
||||
const MainRouter: FC = () => {
|
||||
return (
|
||||
@@ -40,6 +41,7 @@ const MainRouter: FC = () => {
|
||||
<div className='xl:pb-20 pb-32'>
|
||||
<Routes>
|
||||
<Route path={Pages.dashboard} element={<Home />} />
|
||||
<Route path={Pages.support.index} element={<Support />} />
|
||||
<Route path={Pages.services.mine} element={<MyServices />} />
|
||||
<Route path={Pages.services.other} element={<OtherServices />} />
|
||||
<Route path={Pages.services.detail + ':id'} element={<DetailService />} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import LogoImage from '../assets/images/logo.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Card, DocumentText, Element3, Element4, Home2, Logout, Messages3, NotificationStatus, Receipt21, Setting2, Teacher } from 'iconsax-react'
|
||||
import { Card, DocumentText, Element3, Element4, Headphone, Home2, Logout, Messages3, NotificationStatus, Receipt21, Setting2, Teacher } from 'iconsax-react'
|
||||
import SideBarItem from './SideBarItem'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Pages } from '../config/Pages'
|
||||
@@ -53,6 +53,13 @@ const SideBar: FC = () => {
|
||||
isActive={isActive('services')}
|
||||
link={Pages.services.mine}
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<Headphone variant={isActive('support') ? 'Bold' : 'Outline'} color={isActive('support') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.support')}
|
||||
isActive={isActive('support')}
|
||||
link={Pages.support.index}
|
||||
/>
|
||||
<SideBarItem
|
||||
icon={<Element3 variant={isActive('other-service') ? 'Bold' : 'Outline'} color={isActive('other-service') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.other_service')}
|
||||
|
||||
Reference in New Issue
Block a user