sidebar + permission header
This commit is contained in:
+12
-2
@@ -610,7 +610,9 @@
|
||||
"image_profile": "تصویر حساب کاربری",
|
||||
"image_profile_desc": "تصویر خود را در حساب کاربری قرار دهید.",
|
||||
"upload_image": "آپلود تصویر",
|
||||
"format_image": "فرمت های jpg, png,jpeg.حداکثر ۱مگابایت",
|
||||
"formats": "فرمت های",
|
||||
"max_size": "حداکثر ۱مگابایت",
|
||||
"format_image": " jpg, png,jpeg.",
|
||||
"info_account": "اطلاعات حساب",
|
||||
"auth_after_change_info": "در صورت تغییر اطلاعات حساب باید مجددا احراز هویت انجام شود.",
|
||||
"username": "نام کاربری",
|
||||
@@ -623,7 +625,15 @@
|
||||
"address_live": "آدرس محل سکونت خود را با دقت وارد کنید",
|
||||
"province": "استان",
|
||||
"city": "شهر",
|
||||
"postal_code": "کد پستی"
|
||||
"postal_code": "کد پستی",
|
||||
"username_available": "نام کاربری موجود",
|
||||
"username_not_available": "نام کاربری موجود نیست",
|
||||
"username_save_success": "نام کاربری با موفقیت ذخیره شد",
|
||||
"image_uploaded_successfully": "تصویر با موفقیت آپلود شد",
|
||||
"confrim_email": "تایید ایمیل",
|
||||
"confrim": "تایید صورتحساب ",
|
||||
"link_email": "یک لینک برای تایید ایمیل برای شما ارسال شد.",
|
||||
"email_not_verified": "ایمیل تایید نشده"
|
||||
},
|
||||
"email": "ایمیل",
|
||||
"customer": {
|
||||
|
||||
+229
-143
@@ -1,16 +1,104 @@
|
||||
import { FC } from 'react'
|
||||
import { FC, useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AvatarImage from '../../assets/images/avatar_image.png'
|
||||
import Button from '../../components/Button'
|
||||
import { DocumentUpload, Edit } from 'iconsax-react'
|
||||
import { DocumentUpload } from 'iconsax-react'
|
||||
import Input from '../../components/Input'
|
||||
import DatePickerComponent from '../../components/DatePicker'
|
||||
import Select from '../../components/Select'
|
||||
import Textarea from '../../components/Textarea'
|
||||
import { useGetProfile, useUpdateProfile } from './hooks/useProfileData'
|
||||
import Username from './components/Username'
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
import { UpdateProfileType } from './types/ProfileTypes'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import Email from './components/Email'
|
||||
import Phone from './components/Phone'
|
||||
import { useGetProvines } from '../customer/hooks/useCustomerData'
|
||||
import { useSingleUpload } from '../service/hooks/useServiceData'
|
||||
|
||||
const Profile: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [file, setFile] = useState<File>()
|
||||
const getProvines = useGetProvines()
|
||||
const getProfile = useGetProfile()
|
||||
const singleUpload = useSingleUpload()
|
||||
const updateProfile = useUpdateProfile()
|
||||
|
||||
const formik = useFormik<UpdateProfileType>({
|
||||
initialValues: {
|
||||
cityId: '',
|
||||
userAddress: '',
|
||||
postalCode: '',
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
cityId: Yup.string().required(t('errors.required')),
|
||||
userAddress: Yup.string().required(t('errors.required')),
|
||||
postalCode: Yup.string().required(t('errors.required')),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
updateProfile.mutate(values, {
|
||||
onSuccess: () => {
|
||||
toast.success(t('success'))
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (getProfile.data) {
|
||||
formik.setValues({
|
||||
cityId: getProfile.data.data.user.city?.id || '',
|
||||
userAddress: getProfile.data.data.user.userAddress || '',
|
||||
postalCode: getProfile.data.data.user.postalCode || '',
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getProfile.data])
|
||||
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
|
||||
if (acceptedFiles.length > 0) {
|
||||
const file = acceptedFiles[0];
|
||||
if (file.type.startsWith('image/')) {
|
||||
setFile(file);
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
singleUpload.mutate(formData, {
|
||||
onSuccess: (data) => {
|
||||
const params: UpdateProfileType = {
|
||||
profilePic: data.data?.url
|
||||
}
|
||||
updateProfile.mutate(params, {
|
||||
onSuccess: () => {
|
||||
toast.success(t('profile.image_uploaded_successfully'))
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
} else {
|
||||
toast.error(t('errors.is_not_image'))
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
const { getRootProps, getInputProps } = useDropzone({ onDrop })
|
||||
|
||||
return (
|
||||
<div className='mt-4 '>
|
||||
@@ -18,161 +106,159 @@ const Profile: FC = () => {
|
||||
{t('profile.account_user')}
|
||||
</div>
|
||||
|
||||
<div className='bg-white rounded-3xl xl:p-6 p-4 mt-8 text-sm'>
|
||||
<div className='xl:mt-10 mt-4 flex xl:flex-row flex-col xl:gap-0 gap-7 xl:items-center border-b pb-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('profile.image_profile')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
{t('profile.image_profile_desc')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 flex xl:gap-6 gap-4'>
|
||||
<img src={AvatarImage} className='xl:size-20 size-14 rounded-full object-cover' />
|
||||
<div>
|
||||
<Button
|
||||
className='xl:w-[160px] w-fit px-4 xl:;px-0 h-8 xl:h-10 text-xs xl:text-sm'
|
||||
>
|
||||
<div className='flex items-center gap-3'>
|
||||
<DocumentUpload color='white' size={18} />
|
||||
<div>{t('profile.upload_image')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
<p className='mt-3 text-description'>
|
||||
{t('profile.format_image')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7 border-b pb-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('profile.info_account')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
{t('profile.auth_after_change_info')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 '>
|
||||
<div className='flex items-end xl:gap-6 gap-3'>
|
||||
<Input
|
||||
label={t('profile.username')}
|
||||
value={'mehrdad'}
|
||||
readOnly
|
||||
/>
|
||||
<div className='flex relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||
{
|
||||
getProfile.isPending || getProvines.isPending ?
|
||||
<PageLoading />
|
||||
:
|
||||
<div className='bg-white rounded-3xl xl:p-6 p-4 mt-8 text-sm'>
|
||||
<div className='xl:mt-10 mt-4 flex xl:flex-row flex-col xl:gap-0 gap-7 xl:items-center border-b pb-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('edit')}
|
||||
{t('profile.image_profile')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
{t('profile.image_profile_desc')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 flex xl:gap-6 gap-4'>
|
||||
<img src={file ? URL.createObjectURL(file) : getProfile.data?.data?.user?.profilePic ? getProfile.data?.data?.user?.profilePic : AvatarImage} className='xl:size-20 size-14 rounded-full object-cover' />
|
||||
<div>
|
||||
<Button
|
||||
isLoading={singleUpload.isPending || updateProfile.isPending}
|
||||
className='xl:w-[160px] w-fit px-4 xl:;px-0 h-8 xl:h-10 text-xs xl:text-sm'
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<div {...getRootProps()} className='flex items-center gap-3'>
|
||||
<DocumentUpload color='white' size={18} />
|
||||
<div>{t('profile.upload_image')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
<p className='mt-3 items-center flex gap-0.5 text-description'>
|
||||
{t('profile.formats')}
|
||||
<div className='-mt-0.5'>{t('profile.format_image')}</div>
|
||||
<span>{t('profile.max_size')}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('email')}
|
||||
value={'info@example.com'}
|
||||
readOnly
|
||||
/>
|
||||
<div className='flex relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7 border-b pb-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('edit')}
|
||||
{t('profile.info_account')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
{t('profile.auth_after_change_info')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 '>
|
||||
<Username
|
||||
username={getProfile.data?.data?.user?.userName}
|
||||
/>
|
||||
<Email
|
||||
email={getProfile.data?.data?.user?.email}
|
||||
isVerified={getProfile.data?.data?.user?.emailVerified}
|
||||
/>
|
||||
<Phone
|
||||
phone={getProfile.data?.data?.user?.phone}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7 border-b pb-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('profile.personal_information')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
{t('profile.enter_carefully_your_information')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<DatePickerComponent
|
||||
label={t('profile.date_of_birth')}
|
||||
onChange={() => { }}
|
||||
placeholder=''
|
||||
defaulValue={getProfile.data?.data?.user?.birthDate}
|
||||
/>
|
||||
|
||||
<div className='xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('profile.national_code')}
|
||||
value={getProfile.data?.data?.user?.nationalCode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('profile.phone_call')}
|
||||
value={'۰۹۱۲۹۲۸۳۳۹۵'}
|
||||
readOnly
|
||||
/>
|
||||
<div className='flex relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||
|
||||
{/* <div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('edit')}
|
||||
{t('profile.address')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
{t('profile.address_live')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<div className='rowTwoInput'>
|
||||
<Select
|
||||
label={t('profile.province')}
|
||||
items={getProvines.data?.data?.provinces.map((item: ProvinesItemType) => ({ label: item.name, value: item.id }))}
|
||||
onChange={(e) => setProvinesId(e.target.value)}
|
||||
className='bg-white border'
|
||||
placeholder={t('select')}
|
||||
value={provinesId}
|
||||
/>
|
||||
<Select
|
||||
label={t('profile.city')}
|
||||
items={getCities.data?.data?.cities ? getCities.data?.data?.cities.map((item: ProvinesItemType) => ({ label: item.name, value: item.id })) : []}
|
||||
placeholder={t('select')}
|
||||
{...formik.getFieldProps('cityId')}
|
||||
error_text={formik.touched.cityId && formik.errors.cityId ? formik.errors.cityId : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7 border-b pb-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('profile.personal_information')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
{t('profile.enter_carefully_your_information')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<DatePickerComponent
|
||||
label={t('profile.date_of_birth')}
|
||||
onChange={() => { }}
|
||||
placeholder=''
|
||||
defaulValue='1400-01-01'
|
||||
/>
|
||||
<div className='xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('profile.postal_code')}
|
||||
{...formik.getFieldProps('postalCode')}
|
||||
error_text={formik.touched.postalCode && formik.errors.postalCode ? formik.errors.postalCode : ''}
|
||||
value={formik.values.postalCode}
|
||||
/>
|
||||
</div>
|
||||
<div className='xl:mt-7 mt-4'>
|
||||
<Textarea
|
||||
label={t('profile.address')}
|
||||
defaultValue={formik.values.userAddress}
|
||||
{...formik.getFieldProps('userAddress')}
|
||||
error_text={formik.touched.userAddress && formik.errors.userAddress ? formik.errors.userAddress : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('profile.national_code')}
|
||||
value={'۰۰۱۲۳۴۵۳۴۴'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-end mt-5'>
|
||||
<Button
|
||||
className='xl:w-fit px-10'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={updateProfile.isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle size={20} color='white' />
|
||||
<div>
|
||||
{t('save')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('profile.address')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
{t('profile.address_live')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<div className='rowTwoInput'>
|
||||
<Select
|
||||
label={t('profile.province')}
|
||||
items={[
|
||||
{
|
||||
label: 'تهران',
|
||||
value: 'tehran'
|
||||
}
|
||||
]}
|
||||
className='bg-white border'
|
||||
/>
|
||||
<Select
|
||||
label={t('profile.city')}
|
||||
items={[
|
||||
{
|
||||
label: 'تهران',
|
||||
value: 'tehran'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('profile.postal_code')}
|
||||
value={'۱۲۳۴۵۶۷۸'}
|
||||
/>
|
||||
</div>
|
||||
<div className='xl:mt-7 mt-4'>
|
||||
<Textarea
|
||||
label={t('profile.address')}
|
||||
value={'للورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک استورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='h-20 xl:hidden'></div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className='h-20 xl:hidden'></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { FC, Fragment, useState } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import { Edit, TickCircle } from 'iconsax-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../../../components/Button'
|
||||
import { isEmail } from '../../../config/func'
|
||||
import DefaulModal from '../../../components/DefaulModal'
|
||||
import OTPInput from 'react-otp-input'
|
||||
import { useUpdateEmail } from '../hooks/useProfileData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
type Props = {
|
||||
email: string | null,
|
||||
isVerified: boolean
|
||||
}
|
||||
|
||||
const Email: FC<Props> = (props: Props) => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [email, setEmail] = useState<string>(props.email ? props.email : '')
|
||||
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||
const [showModal, setShowModal] = useState<boolean>(false)
|
||||
const [code, setCode] = useState<string>('')
|
||||
const updateEmail = useUpdateEmail()
|
||||
|
||||
const handleShowModal = () => {
|
||||
updateEmail.mutate({ email: email }, {
|
||||
onSuccess: () => {
|
||||
toast.success(t('profile.link_email'))
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='flex relative items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('email')}
|
||||
value={email}
|
||||
readOnly={isReadOnly}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
{
|
||||
!props.isVerified &&
|
||||
<div className='text-[10px] absolute left-24 text-red-400 top-[34px]'>
|
||||
{t('profile.email_not_verified')}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
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={!isEmail(email)}
|
||||
onClick={handleShowModal}
|
||||
isLoading={updateEmail.isPending}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<DefaulModal
|
||||
open={showModal}
|
||||
close={() => setShowModal(false)}
|
||||
isHeader
|
||||
title_header={t('profile.confrim_email')}
|
||||
>
|
||||
<div className='mt-7'>
|
||||
<div className='text-xs text-center'>
|
||||
کد تایید به ایمیل {email} ارسال شده است.برای تایید کد مربوطه را وارد کنید.
|
||||
</div>
|
||||
|
||||
<div className='mt-10'>کد تایید</div>
|
||||
<div className='mt-2 w-full flex justify-center dltr otp'>
|
||||
<OTPInput
|
||||
value={code}
|
||||
onChange={setCode}
|
||||
shouldAutoFocus
|
||||
renderInput={(props) =>
|
||||
<input
|
||||
{...props}
|
||||
type='tel'
|
||||
autoComplete="one-time-code"
|
||||
inputMode="numeric"
|
||||
className='w-full h-[50px] flex-1 mx-2 bg-white bg-opacity-30 border rounded-2.5' />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-14 flex justify-end border-t border-border pt-8'>
|
||||
<div className='flex gap-5'>
|
||||
<Button
|
||||
className='bg-white bg-opacity-40 text-description w-[150px] text-xs'
|
||||
label='لغو'
|
||||
onClick={() => setShowModal(false)}
|
||||
/>
|
||||
<Button
|
||||
className='w-[150px] text-xs'
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle
|
||||
size={20}
|
||||
color='white'
|
||||
/>
|
||||
<div>{t('wallet.submit_slip')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</DefaulModal>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Email
|
||||
@@ -0,0 +1,143 @@
|
||||
import { FC, Fragment, useState } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import { Edit, TickCircle } from 'iconsax-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../../../components/Button'
|
||||
import DefaulModal from '../../../components/DefaulModal'
|
||||
import OTPInput from 'react-otp-input'
|
||||
import { useUpdatePhone, useVerifyPhone } from '../hooks/useProfileData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { toast } from 'react-toastify'
|
||||
import { VerifyPhoneType } from '../types/ProfileTypes'
|
||||
|
||||
type Props = {
|
||||
phone: string,
|
||||
}
|
||||
|
||||
const Phone: FC<Props> = (props: Props) => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [phone, setPhone] = useState<string>(props.phone)
|
||||
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||
const [showModal, setShowModal] = useState<boolean>(false)
|
||||
const [code, setCode] = useState<string>('')
|
||||
const updatePhone = useUpdatePhone()
|
||||
const verifyPhone = useVerifyPhone()
|
||||
|
||||
const handleShowModal = () => {
|
||||
updatePhone.mutate({ phone: phone }, {
|
||||
onSuccess: () => {
|
||||
setShowModal(true)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
const params: VerifyPhoneType = {
|
||||
code: code,
|
||||
phone: phone
|
||||
}
|
||||
|
||||
verifyPhone.mutate(params, {
|
||||
onSuccess: () => {
|
||||
setShowModal(false)
|
||||
setIsReadOnly(true)
|
||||
toast.success(t('success'))
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error.message[0])
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='flex items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||
<Input
|
||||
label={t('profile.phone_call')}
|
||||
value={phone}
|
||||
readOnly={isReadOnly}
|
||||
onChange={(e) => setPhone(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={phone.length !== 11}
|
||||
onClick={handleShowModal}
|
||||
isLoading={updatePhone.isPending}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<DefaulModal
|
||||
open={showModal}
|
||||
close={() => setShowModal(false)}
|
||||
isHeader
|
||||
title_header={t('profile.confrim_email')}
|
||||
>
|
||||
<div className='mt-7'>
|
||||
<div className='text-xs text-center'>
|
||||
کد تایید به شماره {phone} ارسال شده است.برای تایید کد مربوطه را وارد کنید.
|
||||
</div>
|
||||
|
||||
<div className='mt-10'>کد تایید</div>
|
||||
<div className='mt-2 w-full flex justify-center dltr otp'>
|
||||
<OTPInput
|
||||
value={code}
|
||||
onChange={setCode}
|
||||
shouldAutoFocus
|
||||
numInputs={5}
|
||||
renderInput={(props) =>
|
||||
<input
|
||||
{...props}
|
||||
type='tel'
|
||||
autoComplete="one-time-code"
|
||||
inputMode="numeric"
|
||||
className='w-full h-[50px] flex-1 mx-2 bg-white bg-opacity-30 border rounded-2.5' />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-14 flex justify-end border-t border-border pt-8'>
|
||||
<div className='flex gap-5'>
|
||||
<Button
|
||||
className='bg-white bg-opacity-40 text-description w-[150px] text-xs'
|
||||
label='لغو'
|
||||
onClick={() => setShowModal(false)}
|
||||
/>
|
||||
<Button
|
||||
className='w-[150px] text-xs'
|
||||
onClick={handleSubmit}
|
||||
disabled={code.length !== 5}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle
|
||||
size={20}
|
||||
color='white'
|
||||
/>
|
||||
<div>{t('save')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</DefaulModal>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Phone
|
||||
@@ -0,0 +1,132 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import { Edit } from 'iconsax-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../../../components/Button'
|
||||
import { useCheckUserName, useUpdateProfile } from '../hooks/useProfileData'
|
||||
import { CheckUserNameType, UpdateProfileType } from '../types/ProfileTypes'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
|
||||
type Props = {
|
||||
username: string | null,
|
||||
}
|
||||
const Username: FC<Props> = (props: Props) => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [username, setUsername] = useState<string>(props.username ? props.username : '')
|
||||
const [canSave, setCanSave] = useState<boolean>(false)
|
||||
const [canEdit, setCanEdit] = useState<boolean>(false)
|
||||
const [status, setStatus] = useState<'success' | 'error' | ''>('')
|
||||
const checkUsername = useCheckUserName()
|
||||
const updateProfile = useUpdateProfile()
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
setCanSave(false)
|
||||
if (value.indexOf(' ') === -1) {
|
||||
if (value.length <= 3) {
|
||||
setUsername(value)
|
||||
return
|
||||
}
|
||||
setStatus('')
|
||||
const params: CheckUserNameType = {
|
||||
value: e.target.value,
|
||||
type: 'userName'
|
||||
}
|
||||
setUsername(e.target.value)
|
||||
checkUsername.mutate(params, {
|
||||
onSuccess() {
|
||||
setStatus('success')
|
||||
setCanSave(true)
|
||||
},
|
||||
onError() {
|
||||
setStatus('error')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const params: UpdateProfileType = {
|
||||
userName: username
|
||||
}
|
||||
|
||||
updateProfile.mutate(params, {
|
||||
onSuccess: () => {
|
||||
toast.success(t('profile.username_save_success'))
|
||||
setCanEdit(false)
|
||||
setCanSave(false)
|
||||
setStatus('')
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast.error(error.response?.data?.error?.message?.[0])
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (username !== props.username) {
|
||||
setUsername(props.username ? props.username : '')
|
||||
}
|
||||
}, [props.username])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex items-end xl:gap-6 gap-3'>
|
||||
<div className='relative w-full'>
|
||||
<Input
|
||||
label={t('profile.username')}
|
||||
value={username}
|
||||
readOnly={!canEdit}
|
||||
onChange={handleChange}
|
||||
minLength={3}
|
||||
maxLength={20}
|
||||
/>
|
||||
|
||||
{
|
||||
checkUsername.isPending &&
|
||||
<div className='absolute left-4 top-8'>
|
||||
<div className='loader'></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className='flex relative gap-1 text-[#0047FF] text-xs items-center'>
|
||||
{
|
||||
!canEdit ?
|
||||
<div onClick={() => setCanEdit(true)} className='flex gap-1 cursor-pointer relative -top-3'>
|
||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||
<div>
|
||||
{t('edit')}
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<Button
|
||||
label={t('save')}
|
||||
onClick={handleSave}
|
||||
className='px-4'
|
||||
disabled={!canSave}
|
||||
isLoading={updateProfile.isPending}
|
||||
/>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
status === 'success' ?
|
||||
<div className='text-xs text-green-500 mt-2'>
|
||||
{t('profile.username_available')}
|
||||
</div>
|
||||
:
|
||||
status === 'error' ?
|
||||
<div className='text-xs text-red-500 mt-2'>
|
||||
{t('profile.username_not_available')}
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Username
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import * as api from '../service/ProfileService'
|
||||
import { CheckUserNameType, UpdateEmailType, UpdatePhoneType, UpdateProfileType, VerifyPhoneType } from '../types/ProfileTypes';
|
||||
|
||||
export const useGetProfile = () => {
|
||||
return useQuery({
|
||||
queryKey: ["profile"],
|
||||
queryFn: () => api.getProfile(),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCheckUserName = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: CheckUserNameType) => api.checkUserName(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateProfile = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: UpdateProfileType) => api.updateProfile(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateEmail = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: UpdateEmailType) => api.updateEmail(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdatePhone = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: UpdatePhoneType) => api.updatePhone(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useVerifyPhone = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: VerifyPhoneType) => api.verifyPhone(variables),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import axios from "../../../config/axios";
|
||||
import {
|
||||
CheckUserNameType,
|
||||
UpdateEmailType,
|
||||
UpdatePhoneType,
|
||||
UpdateProfileType,
|
||||
VerifyPhoneType,
|
||||
} from "../types/ProfileTypes";
|
||||
|
||||
export const getProfile = async () => {
|
||||
const { data } = await axios.get(`/users/me`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const checkUserName = async (params: CheckUserNameType) => {
|
||||
const { data } = await axios.post(`/users/check-validity`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateProfile = async (params: UpdateProfileType) => {
|
||||
const { data } = await axios.patch(`/users/update-profile`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateEmail = async (params: UpdateEmailType) => {
|
||||
const { data } = await axios.patch(`/users/change-email`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updatePhone = async (params: UpdatePhoneType) => {
|
||||
const { data } = await axios.patch(`/users/change-phone`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const verifyPhone = async (params: VerifyPhoneType) => {
|
||||
const { data } = await axios.patch(`/users/verify-phone`, params);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
export type CheckUserNameType = {
|
||||
type: "userName";
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type UpdateProfileType = {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
birthDate?: string;
|
||||
userName?: string;
|
||||
profilePic?: string;
|
||||
cityId?: string;
|
||||
postalCode?: string;
|
||||
userAddress?: string;
|
||||
};
|
||||
|
||||
export type UpdateEmailType = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type UpdatePhoneType = {
|
||||
phone: string;
|
||||
};
|
||||
|
||||
export type VerifyPhoneType = {
|
||||
phone: string;
|
||||
code: string;
|
||||
};
|
||||
+49
-28
@@ -2,13 +2,13 @@ import { Cards, Money3, MoneyRecive, TickSquare } from 'iconsax-react'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tabs from '../../components/Tabs'
|
||||
import Online from './components/Online'
|
||||
import CardtoCard from './components/CardtoCard'
|
||||
import Sheba from './components/Sheba'
|
||||
import { useGetWalletBalance } from './hooks/useWalletData'
|
||||
import { NumberFormat } from '../../config/func'
|
||||
|
||||
const Wallet: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const getWalletBalance = useGetWalletBalance()
|
||||
const [activeTab, setActiveTab] = useState<string>('online')
|
||||
|
||||
return (
|
||||
@@ -17,6 +17,15 @@ const Wallet: FC = () => {
|
||||
{t('wallet.increese_wallet')}
|
||||
</div>
|
||||
|
||||
<div className='mt-7 xl:hidden flex xl:px-10 px-6 w-full items-center text-description mx-auto backdrop-blur-md border-2 border-white gap-10 h-[70px] justify-between rounded-[32px] bg-white bg-opacity-45'>
|
||||
<div className='xl:text-base text-sm'>{t('wallet.balance')}</div>
|
||||
|
||||
<div className='h-8 text-black rounded-xl text-sm gap-1 w-fit xl:px-14 px-4 bg-[#EEF0F7] flex items-center'>
|
||||
{NumberFormat(getWalletBalance.data?.data?.balance)}
|
||||
<div>تومان</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex gap-6'>
|
||||
<div className='flex-1'>
|
||||
<Tabs
|
||||
@@ -41,44 +50,56 @@ const Wallet: FC = () => {
|
||||
onChange={setActiveTab}
|
||||
/>
|
||||
|
||||
{
|
||||
{/* {
|
||||
activeTab === 'online' ?
|
||||
<Online />
|
||||
: activeTab === 'card' ?
|
||||
<CardtoCard />
|
||||
: <Sheba />
|
||||
}
|
||||
} */}
|
||||
|
||||
</div>
|
||||
<div className='w-sidebar xl:block hidden'>
|
||||
<div className=' xl:flex hidden px-6 w-full items-center text-description mx-auto backdrop-blur-md border-2 border-white gap-3 h-20 justify-between rounded-[32px] bg-white bg-opacity-45'>
|
||||
<div className='text-xs'>
|
||||
{t('wallet.balance')}
|
||||
</div>
|
||||
|
||||
<div className='bg-white w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
|
||||
<div className='text-sm'>
|
||||
{t('ticket.title_hint')}
|
||||
<div className='h-8 text-black rounded-xl text-xs gap-1 w-fit px-4 bg-[#EEF0F7] flex items-center'>
|
||||
{NumberFormat(getWalletBalance.data?.data?.balance)}
|
||||
<div>تومان</div>
|
||||
</div>
|
||||
</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 className='bg-white py-10 px-5 h-fit rounded-3xl mt-8'>
|
||||
<div className='text-sm'>
|
||||
لطفا قبل از شارژ کیف پول نکات زیر را مد نظر قرار دهید.
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
|
||||
<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='text-description text-xs leading-5'>
|
||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
||||
<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>
|
||||
<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 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>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '../../../components/Input'
|
||||
import UploadBox from '../../../components/UploadBox'
|
||||
import Button from '../../../components/Button'
|
||||
|
||||
const CardtoCard: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
return (
|
||||
<div className='mt-8 bg-white p-8 rounded-3xl'>
|
||||
<div>
|
||||
{t('wallet.card_to_card')}
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label={t('wallet.amount')}
|
||||
placeholder={t('wallet.enter_amount_card')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<UploadBox
|
||||
label={t('wallet.upload_deposit_slip')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-10 flex justify-end'>
|
||||
<Button
|
||||
label={t('wallet.submit_slip')}
|
||||
className='w-[180px]'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardtoCard
|
||||
@@ -1,111 +0,0 @@
|
||||
import { FC, useState } from 'react'
|
||||
import Button from '../../../components/Button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { clx } from '../../../helpers/utils'
|
||||
import SamanImage from '../../../assets/images/saman.png'
|
||||
import Input from '../../../components/Input'
|
||||
|
||||
const Online: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [priceSelect, setPriceSelect] = useState<number>(1)
|
||||
const [bankSelected, setBankSelected] = useState<number>(1)
|
||||
|
||||
return (
|
||||
<div className='mt-8 bg-white p-8 rounded-3xl'>
|
||||
<div>
|
||||
{t('wallet.online_pay')}
|
||||
</div>
|
||||
|
||||
<div className='mt-8 text-sm'>
|
||||
{t('wallet.select_price')}
|
||||
</div>
|
||||
|
||||
<div className='mt-4 flex gap-4 flex-wrap'>
|
||||
<div onClick={() => setPriceSelect(1)} className={clx(
|
||||
'bg-[#EBEDF5] border border-[#EBEDF5] cursor-pointer flex items-center text-xs h-10 px-6 rounded-lg',
|
||||
priceSelect === 1 && 'border-black'
|
||||
)}>
|
||||
۱۰,۰۰۰,۰۰۰ تومان
|
||||
</div>
|
||||
<div onClick={() => setPriceSelect(2)} className={clx(
|
||||
'bg-[#EBEDF5] border border-[#EBEDF5] cursor-pointer flex items-center text-xs h-10 px-6 rounded-lg',
|
||||
priceSelect === 2 && 'border-black'
|
||||
)}>
|
||||
۱۰,۰۰۰,۰۰۰ تومان
|
||||
</div>
|
||||
<div onClick={() => setPriceSelect(3)} className={clx(
|
||||
'bg-[#EBEDF5] border border-[#EBEDF5] cursor-pointer flex items-center text-xs h-10 px-6 rounded-lg',
|
||||
priceSelect === 3 && 'border-black'
|
||||
)}>
|
||||
۱۰,۰۰۰,۰۰۰ تومان
|
||||
</div>
|
||||
<div onClick={() => setPriceSelect(4)} className={clx(
|
||||
'bg-[#EBEDF5] border border-[#EBEDF5] cursor-pointer flex items-center text-xs h-10 px-6 rounded-lg',
|
||||
priceSelect === 4 && 'border-black'
|
||||
)}>
|
||||
۱۰,۰۰۰,۰۰۰ تومان
|
||||
</div>
|
||||
<div onClick={() => setPriceSelect(5)} className={clx(
|
||||
'bg-[#EBEDF5] border border-[#EBEDF5] cursor-pointer flex items-center text-xs h-10 px-6 rounded-lg',
|
||||
priceSelect === 5 && 'border-black'
|
||||
)}>
|
||||
۱۰,۰۰۰,۰۰۰ تومان
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-9'>
|
||||
<Input
|
||||
label={t('wallet.optional_price')}
|
||||
placeholder={t('wallet.enter_your_price')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 text-sm'>
|
||||
<div>
|
||||
{t('wallet.select_payment')}
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex gap-4'>
|
||||
<div onClick={() => setBankSelected(1)} className={clx(
|
||||
'xl:h-16 rounded-xl cursor-pointer flex xl:flex-row flex-col gap-4 items-center text-center py-3 xl:py-0 xl:text-right px-4 border border-border xl:w-[240px]',
|
||||
bankSelected === 1 && 'border-black'
|
||||
)}>
|
||||
<img src={SamanImage} className='w-10' />
|
||||
<div>
|
||||
<div>
|
||||
بانک پارسیان
|
||||
</div>
|
||||
<div className='text-xs mt-1 text-description'>
|
||||
تمام کارت های عضو شتاب
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={() => setBankSelected(2)} className={clx(
|
||||
'xl:h-16 rounded-xl cursor-pointer flex xl:flex-row flex-col gap-4 items-center text-center py-3 xl:py-0 xl:text-right px-4 border border-border xl:w-[240px]',
|
||||
bankSelected === 2 && 'border-black'
|
||||
)}>
|
||||
<img src={SamanImage} className='w-10' />
|
||||
<div>
|
||||
<div>
|
||||
بانک پارسیان
|
||||
</div>
|
||||
<div className='text-xs mt-1 text-description'>
|
||||
تمام کارت های عضو شتاب
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-10 flex justify-end'>
|
||||
<Button
|
||||
label={t('wallet.pay')}
|
||||
className='w-[180px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Online
|
||||
@@ -1,41 +0,0 @@
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '../../../components/Input'
|
||||
import UploadBox from '../../../components/UploadBox'
|
||||
import Button from '../../../components/Button'
|
||||
|
||||
const Sheba: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
return (
|
||||
<div className='mt-8 bg-white p-8 rounded-3xl'>
|
||||
<div>
|
||||
{t('wallet.pay_sheba')}
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label={t('wallet.amount')}
|
||||
placeholder={t('wallet.enter_sheba_deposit')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<UploadBox
|
||||
label={t('wallet.upload_deposit_slip')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-10 flex justify-end'>
|
||||
<Button
|
||||
label={t('wallet.submit_slip')}
|
||||
className='w-[180px]'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sheba
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/WalletService";
|
||||
import { DepositTransferType, PaymentType } from "../types/WalletTypes";
|
||||
|
||||
export const useGetGetWays = () => {
|
||||
return useQuery({
|
||||
queryKey: ["getGetWays"],
|
||||
queryFn: api.getGetWays,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetWalletBalance = () => {
|
||||
return useQuery({
|
||||
queryKey: ["wallet-balance"],
|
||||
queryFn: api.getWalletBalance,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePayment = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: PaymentType) => api.payment(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetBankAccount = () => {
|
||||
return useQuery({
|
||||
queryKey: ["bankAccount"],
|
||||
queryFn: api.getBankAccount,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDepositTransfer = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: DepositTransferType) =>
|
||||
api.depositTransfer(variables),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import axios from "../../../config/axios";
|
||||
import { DepositTransferType, PaymentType } from "../types/WalletTypes";
|
||||
|
||||
export const getGetWays = async () => {
|
||||
const { data } = await axios.get(`/payments/gateways`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getWalletBalance = async () => {
|
||||
const { data } = await axios.get(`/wallets/balance`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const payment = async (params: PaymentType) => {
|
||||
const { data } = await axios.post(`/payments/deposit/gateway`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getBankAccount = async () => {
|
||||
const { data } = await axios.get(`/payments/bank-account`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const depositTransfer = async (params: DepositTransferType) => {
|
||||
const { data } = await axios.post(`/payments/deposit/transfer`, params);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
export type PaymentType = {
|
||||
amount: number;
|
||||
gatewayId: string;
|
||||
};
|
||||
|
||||
export type GatewayItemType = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
logoUrl: string;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
nameFa: string;
|
||||
};
|
||||
|
||||
export type MethosTransferType = "CARD_TO_CARD" | "SHEBA";
|
||||
export type DepositTransferType = {
|
||||
amount: number;
|
||||
transferReceiptUrl: string;
|
||||
bankAccountId: string;
|
||||
method: MethosTransferType;
|
||||
};
|
||||
+68
-23
@@ -1,25 +1,30 @@
|
||||
import { FC } from 'react'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import Input from '../components/Input'
|
||||
import { ArrowDown2, Element3, HambergerMenu, Wallet } from 'iconsax-react'
|
||||
import AvatarImage from '../assets/images/Avatar.png'
|
||||
import { ArrowDown2, CloseCircle, Element3, HambergerMenu, Logout, Wallet } from 'iconsax-react'
|
||||
import AvatarImage from '../assets/images/avatar_image.png'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Pages } from '../config/Pages'
|
||||
import Notifications from '../pages/notification/Notification'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
import { clx } from '../helpers/utils'
|
||||
import { useGetProfile } from '../pages/profile/hooks/useProfileData'
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
|
||||
import SideBarItem from './SideBarItem'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
const location = useLocation();
|
||||
const [popoverKey, setPopoverKey] = useState(0);
|
||||
const { t } = useTranslation('global')
|
||||
const { setOpenSidebar, openSidebar, hasSubMenu } = useSharedStore()
|
||||
const { setOpenSidebar, openSidebar } = useSharedStore()
|
||||
const { data } = useGetProfile()
|
||||
|
||||
useEffect(() => {
|
||||
setPopoverKey((prevKey) => prevKey + 1);
|
||||
}, [location.pathname]);
|
||||
|
||||
return (
|
||||
<div className={clx(
|
||||
'fixed z-10 right-4 left-4 xl:right-[291px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-308px)]',
|
||||
hasSubMenu && 'xl:right-[318px] xl:w-[calc(100%-344px)]',
|
||||
)}>
|
||||
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||
|
||||
<div className='min-w-[270px] hidden xl:block'>
|
||||
<Input
|
||||
@@ -32,22 +37,62 @@ const Header: FC = () => {
|
||||
</div>
|
||||
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
||||
<div className='flex xl:gap-6 gap-4 items-center'>
|
||||
<Element3 color='black' className='xl:size-[18px] size-[17px]' />
|
||||
<Link to={Pages.wallet}>
|
||||
<Link to={Pages.services.other}>
|
||||
<Element3 color='black' className='xl:size-[18px] size-[17px]' />
|
||||
</Link>
|
||||
<Link className='xl:hidden' to={Pages.wallet}>
|
||||
<Wallet className='xl:size-[18px] size-[17px]' color='black' />
|
||||
</Link>
|
||||
<Notifications />
|
||||
<div className='flex gap-2 items-center'>
|
||||
<div className='size-6 rounded-full bg-description overflow-hidden'>
|
||||
<Link to={Pages.profile}>
|
||||
<img src={AvatarImage} className='size-full object-cover' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className='xl:flex hidden gap-1 items-center'>
|
||||
<div className='text-xs'>مهرداد مظفری</div>
|
||||
<ArrowDown2 size={14} color='#8C90A3' />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
data && (
|
||||
<Popover className="relative" key={popoverKey}>
|
||||
<PopoverButton >
|
||||
<div className='flex gap-2 items-center mt-2.5'>
|
||||
<div className='size-6 rounded-full bg-description overflow-hidden'>
|
||||
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
|
||||
</div>
|
||||
<div className='xl:flex hidden gap-1 items-center'>
|
||||
<div className='text-xs'>
|
||||
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
|
||||
</div>
|
||||
<ArrowDown2 size={14} color='#8C90A3' />
|
||||
</div>
|
||||
</div>
|
||||
</PopoverButton>
|
||||
|
||||
<PopoverPanel style={{ minHeight: window.innerWidth < 1140 ? window.innerHeight : undefined }} anchor="bottom" className="flex xl:ml-6 overflow-auto flex-col gap-3 bg-white boxShadow xl:mt-7 z-30 py-4 text-xs rounded-2.5 xl:w-[300px] -mt-[50px] w-full fixed xl:h-fit h-full shadow-md">
|
||||
<div className='absolute xl:hidden top-6 left-6'>
|
||||
<CloseCircle onClick={() => setPopoverKey((prevKey) => prevKey + 1)} size={20} color='black' />
|
||||
</div>
|
||||
<Link to={Pages.profile} className='flex flex-col gap-2 items-center justify-center border-b border-[#EAEDF5] pb-4'>
|
||||
<div className='size-14 rounded-full overflow-hidden'>
|
||||
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
|
||||
</div>
|
||||
|
||||
<div className='text-description'>
|
||||
{data?.data?.user?.email}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className='px-6 -mt-[2px]'>
|
||||
<SideBarItem
|
||||
icon={<Logout color={'black'} size={20} />}
|
||||
title={t('sidebar.logout')}
|
||||
isActive
|
||||
link={Pages.setting}
|
||||
isLogout
|
||||
/>
|
||||
</div>
|
||||
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+22
-12
@@ -16,11 +16,13 @@ import TicketSubMenu from './components/TicketSubMenu'
|
||||
import UsersSubMenu from './components/UsersSubMenu'
|
||||
import LearningSubMenu from './components/LearningSubMenu'
|
||||
import BlogSubMenu from './components/BlogSubMenu'
|
||||
import { useGetProfile } from '../pages/profile/hooks/useProfileData'
|
||||
const SideBar: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { openSidebar, setOpenSidebar, hasSubMenu, setSubMenuName, setSubtMenu, subMenuName } = useSharedStore()
|
||||
const location = useLocation()
|
||||
const { data: profile } = useGetProfile()
|
||||
const isActive = (name: string) => {
|
||||
return location.pathname.includes(name)
|
||||
}
|
||||
@@ -158,12 +160,17 @@ const SideBar: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={clx(
|
||||
'mt-10 px-12 text-header text-sm font-normal',
|
||||
hasSubMenu && 'px-2 text-center'
|
||||
)}>
|
||||
{t('sidebar.other')}
|
||||
</div>
|
||||
{
|
||||
profile?.data?.user?.roles.some((role: { name: string }) => role.name === 'super_admin') &&
|
||||
<div className={clx(
|
||||
'mt-10 px-12 text-header text-sm font-normal',
|
||||
hasSubMenu && 'px-2 text-center'
|
||||
)}>
|
||||
{t('sidebar.other')}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
@@ -186,13 +193,16 @@ const SideBar: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
profile?.data?.user?.roles?.some((role: { name: string }) => role.name === 'super_admin') &&
|
||||
<div className={clx(
|
||||
'mt-10 px-12 text-header text-sm font-normal',
|
||||
hasSubMenu && 'px-2 text-center text-xs'
|
||||
)}>
|
||||
{t('sidebar.mnage_content')}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={clx(
|
||||
'mt-10 px-12 text-header text-sm font-normal',
|
||||
hasSubMenu && 'px-2 text-center text-xs'
|
||||
)}>
|
||||
{t('sidebar.mnage_content')}
|
||||
</div>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
|
||||
Reference in New Issue
Block a user