review
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
VITE_TOKEN_NAME = 'dsc_token'
|
VITE_TOKEN_NAME = 'dsc_token'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token'
|
VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token'
|
||||||
VITE_BASE_URL = 'https://api.danakcorp.com'
|
VITE_BASE_URL = 'https://api.danakcorp.com'
|
||||||
# VITE_BASE_URL = 'http://192.168.0.216:4000'
|
# VITE_BASE_URL = 'http://192.168.1.124:4000'
|
||||||
@@ -15,6 +15,7 @@ import AuthRouter from './router/Auth'
|
|||||||
import { Pages } from './config/Pages'
|
import { Pages } from './config/Pages'
|
||||||
import useConvertNumbers from './hooks/useConvertNumbers';
|
import useConvertNumbers from './hooks/useConvertNumbers';
|
||||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||||
|
import moment from 'moment-jalaali';
|
||||||
|
|
||||||
i18next.init({
|
i18next.init({
|
||||||
interpolation: { escapeValue: false },
|
interpolation: { escapeValue: false },
|
||||||
@@ -47,6 +48,7 @@ const App: FC = () => {
|
|||||||
|
|
||||||
// useNumberFont()
|
// useNumberFont()
|
||||||
useConvertNumbers()
|
useConvertNumbers()
|
||||||
|
moment.loadPersian({ usePersianDigits: true, dialect: "persian-modern" });
|
||||||
|
|
||||||
const [isLogin, setIsLogin] = useState<'checking' | 'isLogin' | 'isNotLogin'>('checking')
|
const [isLogin, setIsLogin] = useState<'checking' | 'isLogin' | 'isNotLogin'>('checking')
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
+54
-17
@@ -18,11 +18,57 @@ type Props = {
|
|||||||
onChangeSearchFinal?: (value: string) => void;
|
onChangeSearchFinal?: (value: string) => void;
|
||||||
} & InputHTMLAttributes<HTMLInputElement>
|
} & InputHTMLAttributes<HTMLInputElement>
|
||||||
|
|
||||||
|
const formatNumber = (value: string | number): string => {
|
||||||
|
if (!value) return '';
|
||||||
|
const inputValue = String(value).replace(/,/g, '');
|
||||||
|
return inputValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
|
};
|
||||||
|
|
||||||
const Input: FC<Props> = (props: Props) => {
|
const Input: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const [formattedValue, setFormattedValue] = useState<string>(
|
||||||
|
props.value ? formatNumber(props.value as string) : ''
|
||||||
|
);
|
||||||
|
|
||||||
const [showPassword, setShowPassword] = useState<boolean>(false)
|
const [showPassword, setShowPassword] = useState<boolean>(false)
|
||||||
const [search, setSearch] = useState<string>('')
|
const [search, setSearch] = useState<string>('')
|
||||||
|
|
||||||
|
const inputClass = clx(
|
||||||
|
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border',
|
||||||
|
props.readOnly && 'bg-gray-100 border-0 text-description',
|
||||||
|
props.variant === 'search' && 'bg-[#EEF0F7] border-0 ps-10',
|
||||||
|
props.className
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (props.seprator) {
|
||||||
|
const inputValue = event.target.value.replace(/,/g, ''); // حذف کاماها
|
||||||
|
const formatted = formatNumber(inputValue);
|
||||||
|
|
||||||
|
// بهروزرسانی مقدار قالببندیشده
|
||||||
|
setFormattedValue(formatted);
|
||||||
|
|
||||||
|
// ارسال مقدار خام به `onChange` والد
|
||||||
|
props.onChange?.({
|
||||||
|
...event,
|
||||||
|
target: {
|
||||||
|
...event.target,
|
||||||
|
value: inputValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
props.onChange?.(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.value) {
|
||||||
|
setFormattedValue(formatNumber(props.value as string));
|
||||||
|
} else {
|
||||||
|
setFormattedValue('');
|
||||||
|
}
|
||||||
|
}, [props.value])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.variant === 'search' && props.onChangeSearchFinal) {
|
if (props.variant === 'search' && props.onChangeSearchFinal) {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
@@ -32,13 +78,6 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}, [search, props])
|
}, [search, props])
|
||||||
|
|
||||||
const inputClass = clx(
|
|
||||||
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border',
|
|
||||||
props.readOnly && 'bg-gray-100 border-0 text-description',
|
|
||||||
props.variant === 'search' && 'bg-[#EEF0F7] border-0 ps-10',
|
|
||||||
props.className
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
@@ -49,8 +88,8 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
<div className='w-full relative mt-1'>
|
<div className='w-full relative mt-1'>
|
||||||
<input {...props} onChange={(e) => {
|
<input {...props} onChange={(e) => {
|
||||||
setSearch(e.target.value)
|
setSearch(e.target.value)
|
||||||
props.onChange?.(e)
|
handleInputChange(e)
|
||||||
}} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
|
}} value={props.seprator ? formattedValue : props.value} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
|
||||||
|
|
||||||
{
|
{
|
||||||
props.type === 'password' &&
|
props.type === 'password' &&
|
||||||
@@ -62,15 +101,13 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
<SearchNormal size={20} color='#8C90A3' className='absolute top-0 w-5 bottom-0 my-auto right-3' />
|
<SearchNormal size={20} color='#8C90A3' className='absolute top-0 w-5 bottom-0 my-auto right-3' />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
props.error_text &&
|
||||||
|
<Error
|
||||||
|
errorText={props.error_text}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
|
||||||
props.error_text &&
|
|
||||||
<Error
|
|
||||||
errorText={props.error_text}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -443,7 +443,8 @@
|
|||||||
"failed": "پرداخت ناموفق!",
|
"failed": "پرداخت ناموفق!",
|
||||||
"desc_failed": "متأسفیم! پرداخت شما انجام نشد. لطفاً دوباره تلاش کنید یا روش پرداخت دیگری انتخاب کنید.",
|
"desc_failed": "متأسفیم! پرداخت شما انجام نشد. لطفاً دوباره تلاش کنید یا روش پرداخت دیگری انتخاب کنید.",
|
||||||
"pending": "وضعیت پرداخت نامشخص است!",
|
"pending": "وضعیت پرداخت نامشخص است!",
|
||||||
"desc_pending": "اگر مبلغی از حساب شما کسر شده ولی سفارش شما بعد از ۷۲ ساعت تأیید نشد، لطفاً با پشتیبانی تماس بگیرید."
|
"desc_pending": "اگر مبلغی از حساب شما کسر شده ولی سفارش شما بعد از ۷۲ ساعت تأیید نشد، لطفاً با پشتیبانی تماس بگیرید.",
|
||||||
|
"home": "صفحه اصلی"
|
||||||
},
|
},
|
||||||
"discount": {
|
"discount": {
|
||||||
"number": "شماره",
|
"number": "شماره",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const Notifications: FC = () => {
|
|||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
<div onClick={() => setShowModal(!showModal)} className='relative cursor-pointer'>
|
<div onClick={() => setShowModal(!showModal)} className='relative cursor-pointer'>
|
||||||
<Notification size={18} color="black" />
|
<Notification size={18} color="black" />
|
||||||
<div className="absolute top-0 right-0 -mt-1 -mr-1 rounded-full bg-red-500 text-white text-[8px] size-3 flex justify-center items-center">
|
<div className="absolute top-0 right-0 -mt-1 -mr-1 rounded-full bg-red-500 text-white text-[8px] size-3 flex pt-0.5 pl-[1px] justify-center items-center">
|
||||||
{getDashboard.data?.data?.notificationCount}
|
{getDashboard.data?.data?.notificationCount}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -93,11 +93,18 @@ const CallBack: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<div className='flex xl:max-w-[560px] w-full gap-2.5'>
|
||||||
label={t('callback.back')}
|
<Button
|
||||||
className='xl:max-w-[560px] mt-8'
|
label={t('callback.home')}
|
||||||
onClick={() => navigate(Pages.transactions)}
|
className='w-full mt-8 bg-transparent border border-black text-black'
|
||||||
/>
|
onClick={() => navigate(Pages.dashboard)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label={t('callback.back')}
|
||||||
|
className='w-full mt-8'
|
||||||
|
onClick={() => navigate(Pages.transactions)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { FC, Fragment } from 'react'
|
import { FC, Fragment } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from '../../components/Button'
|
|
||||||
import Rate from 'rc-rate'
|
import Rate from 'rc-rate'
|
||||||
import ServiceImages from './components/ServiceImages'
|
import ServiceImages from './components/ServiceImages'
|
||||||
import Input from '../../components/Input'
|
import AvatarImage from '../../assets/images/avatar_image.png'
|
||||||
import Textarea from '../../components/Textarea'
|
|
||||||
import AvatarImage from '../../assets/images/Avatar.png'
|
|
||||||
import ServiceHeader from './components/ServiceHeader'
|
import ServiceHeader from './components/ServiceHeader'
|
||||||
import { useGetDetailService } from './hooks/useServiceData'
|
import { useGetDetailService } from './hooks/useServiceData'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
@@ -13,6 +10,8 @@ import PageLoading from '../../components/PageLoading'
|
|||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import { useGetAds } from '../ads/hooks/useAdsData'
|
import { useGetAds } from '../ads/hooks/useAdsData'
|
||||||
import { AdsDisplayLocation } from '../ads/types/AdsTypes'
|
import { AdsDisplayLocation } from '../ads/types/AdsTypes'
|
||||||
|
import CreateReview from './components/CreateReview'
|
||||||
|
import { ReviewItemType } from './types/ServiecTypes'
|
||||||
|
|
||||||
const DetailService: FC = () => {
|
const DetailService: FC = () => {
|
||||||
|
|
||||||
@@ -74,8 +73,9 @@ const DetailService: FC = () => {
|
|||||||
<Rate
|
<Rate
|
||||||
count={5}
|
count={5}
|
||||||
style={{ fontSize: 15 }}
|
style={{ fontSize: 15 }}
|
||||||
value={4}
|
value={getDetailService?.data?.data?.averageRating}
|
||||||
disabled
|
disabled
|
||||||
|
allowHalf
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,111 +92,51 @@ const DetailService: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 flex xl:flex-row flex-col gap-8 items-start'>
|
<div className='mt-8 flex xl:flex-row flex-col gap-8 items-start'>
|
||||||
<div className='bg-white p-6 rounded-3xl xl:w-[360px] w-full'>
|
{
|
||||||
<div>
|
getDetailService.data?.data?.purchased &&
|
||||||
{t('service.submit_comment')}
|
<CreateReview refetch={() => getDetailService.refetch()} />
|
||||||
</div>
|
}
|
||||||
<div className='text-xs mt-4'>
|
|
||||||
{t('service.hint_comment')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-6 text-description text-sm'>
|
|
||||||
<div>
|
|
||||||
{t('service.score')}
|
|
||||||
</div>
|
|
||||||
<div className='mt-2 dltr flex justify-end'>
|
|
||||||
<Rate
|
|
||||||
count={5}
|
|
||||||
style={{ fontSize: 22 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-6'>
|
|
||||||
<Input
|
|
||||||
label={t('service.title')}
|
|
||||||
placeholder={t('service.enter_title_comment')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-6'>
|
|
||||||
<Textarea
|
|
||||||
label={t('service.your_comment')}
|
|
||||||
placeholder={t('service.write_your_comment')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-6'>
|
|
||||||
<Button
|
|
||||||
label={t('service.submit_comment')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex-1 max-w-full bg-white p-6 rounded-3xl'>
|
<div className='flex-1 max-w-full bg-white p-6 rounded-3xl'>
|
||||||
<div className='text-sm'>
|
<div className='text-sm'>
|
||||||
{t('service.users_comments')}
|
{t('service.users_comments')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-5 border border-border rounded-3xl p-6'>
|
{
|
||||||
<div className='flex gap-2 items-center'>
|
getDetailService.data?.data?.danakService?.reviews?.map((item: ReviewItemType) => {
|
||||||
<img src={AvatarImage} className='size-6 rounded-full object-cover' />
|
return (
|
||||||
<div className='text-description text-xs'>
|
<div className='mt-5 border border-border rounded-3xl p-6'>
|
||||||
فروغ براتی
|
<div className='flex gap-2 items-center'>
|
||||||
</div>
|
<img src={item.user.profilePic || AvatarImage} className='size-6 rounded-full object-cover' />
|
||||||
</div>
|
<div className='text-description text-xs'>
|
||||||
|
{item.user?.firstName + ' ' + item.user?.lastName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='mt-4 flex justify-between'>
|
<div className='mt-4 flex justify-between'>
|
||||||
<div className='text-sm max-w-[70%] truncate'>
|
<div className='text-sm max-w-[70%] truncate'>
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
|
{item.title}
|
||||||
</div>
|
</div>
|
||||||
<div className='text-xs text-description'>
|
<div className='text-xs text-description'>
|
||||||
۳ روز پیش
|
{moment(item.createdAt).fromNow()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-2'>
|
<div className='mt-2'>
|
||||||
<Rate
|
<Rate
|
||||||
disabled
|
disabled
|
||||||
value={5}
|
value={item.rating}
|
||||||
count={5}
|
count={5}
|
||||||
style={{ fontSize: 14 }}
|
style={{ fontSize: 14 }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-4 text-[11px] leading-6'>
|
<div className='mt-4 text-[11px] leading-6'>
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه و متخصصان را ...
|
{item.comment}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-5 border border-border rounded-3xl p-6'>
|
)
|
||||||
<div className='flex gap-2 items-center'>
|
})
|
||||||
<img src={AvatarImage} className='size-6 rounded-full object-cover' />
|
}
|
||||||
<div className='text-description text-xs'>
|
|
||||||
فروغ براتی
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-4 flex justify-between'>
|
|
||||||
<div className='text-sm max-w-[70%] truncate'>
|
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
|
|
||||||
</div>
|
|
||||||
<div className='text-xs text-description'>
|
|
||||||
۳ روز پیش
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-2'>
|
|
||||||
<Rate
|
|
||||||
disabled
|
|
||||||
value={5}
|
|
||||||
count={5}
|
|
||||||
style={{ fontSize: 14 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-4 text-[11px] leading-6'>
|
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه و متخصصان را ...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import Rate from 'rc-rate'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import Textarea from '../../../components/Textarea'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { CreateReviewType } from '../types/ServiecTypes'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { useCreateReview } from '../hooks/useServiceData'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import Error from '../../../components/Error'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
refetch: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateReview: FC<Props> = ({ refetch }) => {
|
||||||
|
|
||||||
|
const { id } = useParams()
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
|
const createReview = useCreateReview()
|
||||||
|
|
||||||
|
const formik = useFormik<CreateReviewType>({
|
||||||
|
initialValues: {
|
||||||
|
title: '',
|
||||||
|
comment: '',
|
||||||
|
rating: undefined,
|
||||||
|
id: id ? id : ''
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
title: Yup.string().required(t('errors.required')),
|
||||||
|
comment: Yup.string().required(t('errors.required')),
|
||||||
|
rating: Yup.string().required(t('errors.required')),
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
createReview.mutate(values, {
|
||||||
|
onSuccess: () => {
|
||||||
|
formik.resetForm()
|
||||||
|
refetch()
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='bg-white p-6 rounded-3xl xl:w-[360px] w-full'>
|
||||||
|
<div>
|
||||||
|
{t('service.submit_comment')}
|
||||||
|
</div>
|
||||||
|
<div className='text-xs mt-4'>
|
||||||
|
{t('service.hint_comment')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6 text-description text-sm'>
|
||||||
|
<div>
|
||||||
|
{t('service.score')}
|
||||||
|
</div>
|
||||||
|
<div className='mt-2 dltr flex justify-end'>
|
||||||
|
<Rate
|
||||||
|
count={5}
|
||||||
|
style={{ fontSize: 22 }}
|
||||||
|
value={formik.values.rating}
|
||||||
|
onChange={(value) => formik.setFieldValue('rating', value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{formik.errors.rating && formik.touched.rating && <Error errorText={formik.errors.rating} />}
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Input
|
||||||
|
label={t('service.title')}
|
||||||
|
placeholder={t('service.enter_title_comment')}
|
||||||
|
{...formik.getFieldProps('title')}
|
||||||
|
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Textarea
|
||||||
|
label={t('service.your_comment')}
|
||||||
|
placeholder={t('service.write_your_comment')}
|
||||||
|
{...formik.getFieldProps('comment')}
|
||||||
|
error_text={formik.touched.comment && formik.errors.comment ? formik.errors.comment : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Button
|
||||||
|
label={t('service.submit_comment')}
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={createReview.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateReview
|
||||||
@@ -82,13 +82,13 @@ const ServiceHeader: FC<Props> = (props: Props) => {
|
|||||||
<div className='flex gap-4 mt-5 xl:mt-0 items-center justify-between xl:justify-normal'>
|
<div className='flex gap-4 mt-5 xl:mt-0 items-center justify-between xl:justify-normal'>
|
||||||
<div className='text-xs whitespace-nowrap flex gap-1 items-center'>
|
<div className='text-xs whitespace-nowrap flex gap-1 items-center'>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
{
|
{/* {
|
||||||
finalPrice !== price &&
|
finalPrice !== price &&
|
||||||
<div className='line-through text-xs text-description'>
|
<div className='line-through text-xs text-description'>
|
||||||
{NumberFormat(Number(price))}
|
{NumberFormat(Number(price))}
|
||||||
</div>
|
</div>
|
||||||
}
|
} */}
|
||||||
<div>{NumberFormat(Number(finalPrice))}</div>
|
<div>{NumberFormat(Number(price))}</div>
|
||||||
</div>
|
</div>
|
||||||
<span>{planText}</span>
|
<span>{planText}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import * as api from "../service/ServiceService";
|
import * as api from "../service/ServiceService";
|
||||||
import { BuyServiceType } from "../types/ServiecTypes";
|
import { BuyServiceType, CreateReviewType } from "../types/ServiecTypes";
|
||||||
|
|
||||||
export const useGetSuggestedServices = () => {
|
export const useGetSuggestedServices = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -44,3 +44,9 @@ export const useGetMyServices = (search: string) => {
|
|||||||
queryFn: () => api.getMyServices(search),
|
queryFn: () => api.getMyServices(search),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCreateReview = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: CreateReviewType) => api.createReview(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import axios from "../../../config/axios";
|
import axios from "../../../config/axios";
|
||||||
import { BuyServiceType } from "../types/ServiecTypes";
|
import { BuyServiceType, CreateReviewType } from "../types/ServiecTypes";
|
||||||
|
|
||||||
export const getServiceSuggestedDanak = async () => {
|
export const getServiceSuggestedDanak = async () => {
|
||||||
const { data } = await axios.get(`/danak-services/suggested`);
|
const { data } = await axios.get(`/danak-services/suggested`);
|
||||||
@@ -32,3 +32,11 @@ export const getMyServices = async (search: string) => {
|
|||||||
const { data } = await axios.get(`/subscriptions/user?q=${search}`);
|
const { data } = await axios.get(`/subscriptions/user?q=${search}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createReview = async (params: CreateReviewType) => {
|
||||||
|
const { data } = await axios.post(
|
||||||
|
`/danak-services/${params.id}/reviews`,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -119,3 +119,25 @@ export type BuyServiceType = {
|
|||||||
description: string;
|
description: string;
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateReviewType = {
|
||||||
|
id: string;
|
||||||
|
comment: string;
|
||||||
|
rating?: number;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReviewItemType = {
|
||||||
|
comment: string;
|
||||||
|
createdAt: string;
|
||||||
|
id: string;
|
||||||
|
rating: number;
|
||||||
|
status: "APPROVED" | "PENDING" | "REJECTED";
|
||||||
|
title: string;
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
profilePic: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -81,10 +81,16 @@ const ChangePassword: FC = () => {
|
|||||||
|
|
||||||
<div className='mt-9 xl:block hidden'>
|
<div className='mt-9 xl:block hidden'>
|
||||||
<Button
|
<Button
|
||||||
label={t('setting.save_change')}
|
className='w-fit px-4'
|
||||||
className='w-32'
|
|
||||||
onClick={() => formik.handleSubmit()}
|
onClick={() => formik.handleSubmit()}
|
||||||
/>
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<TickCircle size={20} color='white' />
|
||||||
|
<div>
|
||||||
|
{t('setting.save_change')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -110,10 +116,16 @@ const ChangePassword: FC = () => {
|
|||||||
|
|
||||||
<div className=' xl:hidden block'>
|
<div className=' xl:hidden block'>
|
||||||
<Button
|
<Button
|
||||||
label={t('setting.save_change')}
|
className='w-32 hidden xl:block'
|
||||||
className='w-32'
|
|
||||||
onClick={() => formik.handleSubmit()}
|
onClick={() => formik.handleSubmit()}
|
||||||
/>
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<TickCircle size={20} color='white' />
|
||||||
|
<div>
|
||||||
|
{t('setting.save_change')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='justify-end xl:hidden flex'>
|
<div className='justify-end xl:hidden flex'>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Input from '../../components/Input'
|
|||||||
import Textarea from '../../components/Textarea'
|
import Textarea from '../../components/Textarea'
|
||||||
import UploadBox from '../../components/UploadBox'
|
import UploadBox from '../../components/UploadBox'
|
||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
import { CloseCircle, InfoCircle, Paperclip2 } from 'iconsax-react'
|
import { CloseCircle, InfoCircle, Paperclip2, Send2 } from 'iconsax-react'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { useAddMessageTicket, useCloseTicket, useGetMessages, useMultiUpload } from './hooks/useTicketData'
|
import { useAddMessageTicket, useCloseTicket, useGetMessages, useMultiUpload } from './hooks/useTicketData'
|
||||||
import PageLoading from '../../components/PageLoading'
|
import PageLoading from '../../components/PageLoading'
|
||||||
@@ -212,11 +212,17 @@ const TicketDetail: FC = () => {
|
|||||||
|
|
||||||
<div className='flex justify-end mt-6'>
|
<div className='flex justify-end mt-6'>
|
||||||
<Button
|
<Button
|
||||||
label={t('ticket.send')}
|
className='xl:max-w-[100px]'
|
||||||
className='max-w-[100px]'
|
|
||||||
onClick={() => formik.handleSubmit()}
|
onClick={() => formik.handleSubmit()}
|
||||||
isLoading={addMessage.isPending || multiUpload.isPending}
|
isLoading={addMessage.isPending || multiUpload.isPending}
|
||||||
/>
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<Send2 size={20} color='white' />
|
||||||
|
<div>
|
||||||
|
{t('ticket.send')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ const CardtoCard: FC = () => {
|
|||||||
name='amount'
|
name='amount'
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
error_text={formik.touched.amount && formik.errors.amount ? formik.errors.amount : ''}
|
error_text={formik.touched.amount && formik.errors.amount ? formik.errors.amount : ''}
|
||||||
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ const Online: FC = () => {
|
|||||||
placeholder={t('wallet.enter_your_price')}
|
placeholder={t('wallet.enter_your_price')}
|
||||||
onChange={(e) => setDesiredAmount(Number(e.target.value))}
|
onChange={(e) => setDesiredAmount(Number(e.target.value))}
|
||||||
value={desiredAmount}
|
value={desiredAmount}
|
||||||
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ const CardtoCard: FC = () => {
|
|||||||
name='amount'
|
name='amount'
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
error_text={formik.touched.amount && formik.errors.amount ? formik.errors.amount : ''}
|
error_text={formik.touched.amount && formik.errors.amount ? formik.errors.amount : ''}
|
||||||
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user