short description + edit first name last name + ...

This commit is contained in:
hamid zarghami
2025-08-18 16:10:49 +03:30
parent 00aa85477a
commit 87c5908177
8 changed files with 190 additions and 7 deletions
+9
View File
@@ -256,3 +256,12 @@ body {
.plan:nth-child(even) { .plan:nth-child(even) {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
@layer utilities {
.line-clamp-4 {
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
+3 -1
View File
@@ -54,7 +54,7 @@
"menu": "منو", "menu": "منو",
"mainpage": "صفحه اصلی", "mainpage": "صفحه اصلی",
"myservice": "سرویس های من", "myservice": "سرویس های من",
"other_service": ایر سرویس ها", "other_service": "سرویس های داناک",
"receipt_list": "لیست صورتحساب", "receipt_list": "لیست صورتحساب",
"transactions": "تراکنش ها", "transactions": "تراکنش ها",
"other": "سایر", "other": "سایر",
@@ -397,6 +397,8 @@
"enter_carefully_your_information": "اطلاعت شخصی خود را با دقت وارد کنید.", "enter_carefully_your_information": "اطلاعت شخصی خود را با دقت وارد کنید.",
"date_of_birth": "تاریخ تولد", "date_of_birth": "تاریخ تولد",
"national_code": "کد ملی", "national_code": "کد ملی",
"first_name": "نام",
"last_name": "نام خانوادگی",
"address": "آدرس", "address": "آدرس",
"address_live": "آدرس محل سکونت خود را با دقت وارد کنید", "address_live": "آدرس محل سکونت خود را با دقت وارد کنید",
"province": "استان", "province": "استان",
+10
View File
@@ -18,6 +18,8 @@ import { useFormik } from 'formik'
import * as Yup from 'yup' import * as Yup from 'yup'
import Email from './components/Email' import Email from './components/Email'
import Phone from './components/Phone' import Phone from './components/Phone'
import FirstName from './components/FirstName.tsx'
import LastName from './components/LastName.tsx'
const Profile: FC = () => { const Profile: FC = () => {
@@ -34,6 +36,8 @@ const Profile: FC = () => {
cityId: '', cityId: '',
userAddress: '', userAddress: '',
postalCode: '', postalCode: '',
firstName: '',
lastName: '',
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
cityId: Yup.string().required(t('errors.required')), cityId: Yup.string().required(t('errors.required')),
@@ -59,6 +63,8 @@ const Profile: FC = () => {
cityId: getProfile.data.data.user.city?.id || '', cityId: getProfile.data.data.user.city?.id || '',
userAddress: getProfile.data.data.user.userAddress || '', userAddress: getProfile.data.data.user.userAddress || '',
postalCode: getProfile.data.data.user.postalCode || '', postalCode: getProfile.data.data.user.postalCode || '',
firstName: getProfile.data.data.user.firstName || '',
lastName: getProfile.data.data.user.lastName || '',
}) })
if (getProfile.data.data.user.city) { if (getProfile.data.data.user.city) {
} }
@@ -190,6 +196,10 @@ const Profile: FC = () => {
value={getProfile.data?.data?.user?.nationalCode} value={getProfile.data?.data?.user?.nationalCode}
/> />
</div> </div>
<FirstName firstName={getProfile.data?.data?.user?.firstName} />
<LastName lastName={getProfile.data?.data?.user?.lastName} />
</div> </div>
</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
+71
View File
@@ -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
+6 -2
View File
@@ -33,11 +33,15 @@ const BuyService: FC = () => {
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
businessName: Yup.string().required(t('errors.required')), 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')) planId: Yup.string().required(t('errors.required'))
}), }),
onSubmit: (values) => { onSubmit: (values) => {
if (!values.businessPhone) {
values.businessPhone = undefined
}
if (!values.description) {
values.description = undefined
}
buyService.mutate(values, { buyService.mutate(values, {
onSuccess: (data) => { onSuccess: (data) => {
toast(t('success'), 'success') toast(t('success'), 'success')
+18 -2
View File
@@ -1,4 +1,4 @@
import { FC, Fragment } from 'react' import { FC, Fragment, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Rate from 'rc-rate' import Rate from 'rc-rate'
import ServiceImages from './components/ServiceImages' import ServiceImages from './components/ServiceImages'
@@ -20,6 +20,7 @@ const DetailService: FC = () => {
const getAdsLeft = useGetAds(AdsDisplayLocation.SINGLE_SERVICE_PAGE) const getAdsLeft = useGetAds(AdsDisplayLocation.SINGLE_SERVICE_PAGE)
const getDetailService = useGetServiceBySlug(id ? id : '') const getDetailService = useGetServiceBySlug(id ? id : '')
const [isExpanded, setIsExpanded] = useState(false)
return ( return (
<div className='w-full flex gap-6 mt-4'> <div className='w-full flex gap-6 mt-4'>
@@ -87,8 +88,23 @@ const DetailService: FC = () => {
<div> <div>
{t('service.compelete_description')} {t('service.compelete_description')}
</div> </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> </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>
<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'>
+2 -2
View File
@@ -120,8 +120,8 @@ export type MyServicesItem = {
export type BuyServiceType = { export type BuyServiceType = {
planId: string; planId: string;
businessName: string; businessName: string;
businessPhone: string; businessPhone?: string;
description: string; description?: string;
serviceId: string; serviceId: string;
}; };