short description + edit first name last name + ...
This commit is contained in:
@@ -256,3 +256,12 @@ body {
|
||||
.plan:nth-child(even) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.line-clamp-4 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -54,7 +54,7 @@
|
||||
"menu": "منو",
|
||||
"mainpage": "صفحه اصلی",
|
||||
"myservice": "سرویس های من",
|
||||
"other_service": "سایر سرویس ها",
|
||||
"other_service": "سرویس های داناک",
|
||||
"receipt_list": "لیست صورتحساب",
|
||||
"transactions": "تراکنش ها",
|
||||
"other": "سایر",
|
||||
@@ -397,6 +397,8 @@
|
||||
"enter_carefully_your_information": "اطلاعت شخصی خود را با دقت وارد کنید.",
|
||||
"date_of_birth": "تاریخ تولد",
|
||||
"national_code": "کد ملی",
|
||||
"first_name": "نام",
|
||||
"last_name": "نام خانوادگی",
|
||||
"address": "آدرس",
|
||||
"address_live": "آدرس محل سکونت خود را با دقت وارد کنید",
|
||||
"province": "استان",
|
||||
|
||||
@@ -18,6 +18,8 @@ import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import Email from './components/Email'
|
||||
import Phone from './components/Phone'
|
||||
import FirstName from './components/FirstName.tsx'
|
||||
import LastName from './components/LastName.tsx'
|
||||
|
||||
const Profile: FC = () => {
|
||||
|
||||
@@ -34,6 +36,8 @@ const Profile: FC = () => {
|
||||
cityId: '',
|
||||
userAddress: '',
|
||||
postalCode: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
cityId: Yup.string().required(t('errors.required')),
|
||||
@@ -59,6 +63,8 @@ const Profile: FC = () => {
|
||||
cityId: getProfile.data.data.user.city?.id || '',
|
||||
userAddress: getProfile.data.data.user.userAddress || '',
|
||||
postalCode: getProfile.data.data.user.postalCode || '',
|
||||
firstName: getProfile.data.data.user.firstName || '',
|
||||
lastName: getProfile.data.data.user.lastName || '',
|
||||
})
|
||||
if (getProfile.data.data.user.city) {
|
||||
}
|
||||
@@ -190,6 +196,10 @@ const Profile: FC = () => {
|
||||
value={getProfile.data?.data?.user?.nationalCode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FirstName firstName={getProfile.data?.data?.user?.firstName} />
|
||||
|
||||
<LastName lastName={getProfile.data?.data?.user?.lastName} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import Button from '../../../components/Button'
|
||||
import { Edit } from 'iconsax-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useUpdateProfile } from '../hooks/useProfileData'
|
||||
import { UpdateProfileType } from '../types/ProfileTypes'
|
||||
import { toast } from '../../../components/Toast'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
|
||||
type Props = {
|
||||
firstName: string | null | undefined
|
||||
}
|
||||
|
||||
const FirstName: FC<Props> = (props: Props) => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [value, setValue] = useState<string>(props.firstName || '')
|
||||
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||
const updateProfile = useUpdateProfile()
|
||||
|
||||
useEffect(() => {
|
||||
setValue(props.firstName || '')
|
||||
}, [props.firstName])
|
||||
|
||||
const handleSave = () => {
|
||||
const params: UpdateProfileType = { firstName: value }
|
||||
updateProfile.mutate(params, {
|
||||
onSuccess: () => {
|
||||
toast(t('success'), 'success')
|
||||
setIsReadOnly(true)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const isDisabled = value.trim().length === 0 || value === (props.firstName || '')
|
||||
|
||||
return (
|
||||
<div className='flex items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('profile.first_name')}
|
||||
value={value}
|
||||
readOnly={isReadOnly}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
{
|
||||
isReadOnly ? (
|
||||
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||
<div>{t('edit')}</div>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
label={t('save')}
|
||||
className='px-4 w-fit'
|
||||
disabled={isDisabled}
|
||||
onClick={handleSave}
|
||||
isLoading={updateProfile.isPending}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FirstName
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import Button from '../../../components/Button'
|
||||
import { Edit } from 'iconsax-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useUpdateProfile } from '../hooks/useProfileData'
|
||||
import { UpdateProfileType } from '../types/ProfileTypes'
|
||||
import { toast } from '../../../components/Toast'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
|
||||
type Props = {
|
||||
lastName: string | null | undefined
|
||||
}
|
||||
|
||||
const LastName: FC<Props> = (props: Props) => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [value, setValue] = useState<string>(props.lastName || '')
|
||||
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||
const updateProfile = useUpdateProfile()
|
||||
|
||||
useEffect(() => {
|
||||
setValue(props.lastName || '')
|
||||
}, [props.lastName])
|
||||
|
||||
const handleSave = () => {
|
||||
const params: UpdateProfileType = { lastName: value }
|
||||
updateProfile.mutate(params, {
|
||||
onSuccess: () => {
|
||||
toast(t('success'), 'success')
|
||||
setIsReadOnly(true)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const isDisabled = value.trim().length === 0 || value === (props.lastName || '')
|
||||
|
||||
return (
|
||||
<div className='flex items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('profile.last_name')}
|
||||
value={value}
|
||||
readOnly={isReadOnly}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
{
|
||||
isReadOnly ? (
|
||||
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||
<div>{t('edit')}</div>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
label={t('save')}
|
||||
className='px-4 w-fit'
|
||||
disabled={isDisabled}
|
||||
onClick={handleSave}
|
||||
isLoading={updateProfile.isPending}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LastName
|
||||
|
||||
|
||||
@@ -33,11 +33,15 @@ const BuyService: FC = () => {
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
businessName: Yup.string().required(t('errors.required')),
|
||||
businessPhone: Yup.string().required(t('errors.required')),
|
||||
description: Yup.string().required(t('errors.required')),
|
||||
planId: Yup.string().required(t('errors.required'))
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
if (!values.businessPhone) {
|
||||
values.businessPhone = undefined
|
||||
}
|
||||
if (!values.description) {
|
||||
values.description = undefined
|
||||
}
|
||||
buyService.mutate(values, {
|
||||
onSuccess: (data) => {
|
||||
toast(t('success'), 'success')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, Fragment } from 'react'
|
||||
import { FC, Fragment, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Rate from 'rc-rate'
|
||||
import ServiceImages from './components/ServiceImages'
|
||||
@@ -20,6 +20,7 @@ const DetailService: FC = () => {
|
||||
const getAdsLeft = useGetAds(AdsDisplayLocation.SINGLE_SERVICE_PAGE)
|
||||
|
||||
const getDetailService = useGetServiceBySlug(id ? id : '')
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
return (
|
||||
<div className='w-full flex gap-6 mt-4'>
|
||||
@@ -87,8 +88,23 @@ const DetailService: FC = () => {
|
||||
<div>
|
||||
{t('service.compelete_description')}
|
||||
</div>
|
||||
<p dangerouslySetInnerHTML={{ __html: getDetailService.data?.data?.danakService?.metaDescription }} className='text-[13px] mt-4 leading-6'>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{ __html: getDetailService.data?.data?.danakService?.metaDescription }}
|
||||
className={`text-[13px] mt-4 leading-6 ${!isExpanded ? 'line-clamp-4' : ''}`}
|
||||
>
|
||||
</p>
|
||||
{
|
||||
(getDetailService.data?.data?.danakService?.metaDescription?.length || 0) > 0 && (
|
||||
<div className='mt-3'>
|
||||
<button
|
||||
onClick={() => setIsExpanded(prev => !prev)}
|
||||
className='text-xs text-blue-600'
|
||||
>
|
||||
{isExpanded ? 'کمتر' : 'دیدن بیشتر'}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex xl:flex-row flex-col gap-8 items-start'>
|
||||
|
||||
@@ -120,8 +120,8 @@ export type MyServicesItem = {
|
||||
export type BuyServiceType = {
|
||||
planId: string;
|
||||
businessName: string;
|
||||
businessPhone: string;
|
||||
description: string;
|
||||
businessPhone?: string;
|
||||
description?: string;
|
||||
serviceId: string;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user