sidebar + permission header
This commit is contained in:
+12
-2
@@ -610,7 +610,9 @@
|
|||||||
"image_profile": "تصویر حساب کاربری",
|
"image_profile": "تصویر حساب کاربری",
|
||||||
"image_profile_desc": "تصویر خود را در حساب کاربری قرار دهید.",
|
"image_profile_desc": "تصویر خود را در حساب کاربری قرار دهید.",
|
||||||
"upload_image": "آپلود تصویر",
|
"upload_image": "آپلود تصویر",
|
||||||
"format_image": "فرمت های jpg, png,jpeg.حداکثر ۱مگابایت",
|
"formats": "فرمت های",
|
||||||
|
"max_size": "حداکثر ۱مگابایت",
|
||||||
|
"format_image": " jpg, png,jpeg.",
|
||||||
"info_account": "اطلاعات حساب",
|
"info_account": "اطلاعات حساب",
|
||||||
"auth_after_change_info": "در صورت تغییر اطلاعات حساب باید مجددا احراز هویت انجام شود.",
|
"auth_after_change_info": "در صورت تغییر اطلاعات حساب باید مجددا احراز هویت انجام شود.",
|
||||||
"username": "نام کاربری",
|
"username": "نام کاربری",
|
||||||
@@ -623,7 +625,15 @@
|
|||||||
"address_live": "آدرس محل سکونت خود را با دقت وارد کنید",
|
"address_live": "آدرس محل سکونت خود را با دقت وارد کنید",
|
||||||
"province": "استان",
|
"province": "استان",
|
||||||
"city": "شهر",
|
"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": "ایمیل",
|
"email": "ایمیل",
|
||||||
"customer": {
|
"customer": {
|
||||||
|
|||||||
+147
-61
@@ -1,16 +1,104 @@
|
|||||||
import { FC } from 'react'
|
import { FC, useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import AvatarImage from '../../assets/images/avatar_image.png'
|
import AvatarImage from '../../assets/images/avatar_image.png'
|
||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
import { DocumentUpload, Edit } from 'iconsax-react'
|
import { DocumentUpload } from 'iconsax-react'
|
||||||
import Input from '../../components/Input'
|
import Input from '../../components/Input'
|
||||||
import DatePickerComponent from '../../components/DatePicker'
|
import DatePickerComponent from '../../components/DatePicker'
|
||||||
import Select from '../../components/Select'
|
import { useGetProfile, useUpdateProfile } from './hooks/useProfileData'
|
||||||
import Textarea from '../../components/Textarea'
|
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 Profile: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
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 (
|
return (
|
||||||
<div className='mt-4 '>
|
<div className='mt-4 '>
|
||||||
@@ -18,6 +106,10 @@ const Profile: FC = () => {
|
|||||||
{t('profile.account_user')}
|
{t('profile.account_user')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
getProfile.isPending || getProvines.isPending ?
|
||||||
|
<PageLoading />
|
||||||
|
:
|
||||||
<div className='bg-white rounded-3xl xl:p-6 p-4 mt-8 text-sm'>
|
<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='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 className='flex-1'>
|
||||||
@@ -29,18 +121,22 @@ const Profile: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-1 flex xl:gap-6 gap-4'>
|
<div className='flex-1 flex xl:gap-6 gap-4'>
|
||||||
<img src={AvatarImage} className='xl:size-20 size-14 rounded-full object-cover' />
|
<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>
|
<div>
|
||||||
<Button
|
<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'
|
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'>
|
<input {...getInputProps()} />
|
||||||
|
<div {...getRootProps()} className='flex items-center gap-3'>
|
||||||
<DocumentUpload color='white' size={18} />
|
<DocumentUpload color='white' size={18} />
|
||||||
<div>{t('profile.upload_image')}</div>
|
<div>{t('profile.upload_image')}</div>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
<p className='mt-3 text-description'>
|
<p className='mt-3 items-center flex gap-0.5 text-description'>
|
||||||
{t('profile.format_image')}
|
{t('profile.formats')}
|
||||||
|
<div className='-mt-0.5'>{t('profile.format_image')}</div>
|
||||||
|
<span>{t('profile.max_size')}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,45 +152,16 @@ const Profile: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-1 '>
|
<div className='flex-1 '>
|
||||||
<div className='flex items-end xl:gap-6 gap-3'>
|
<Username
|
||||||
<Input
|
username={getProfile.data?.data?.user?.userName}
|
||||||
label={t('profile.username')}
|
|
||||||
value={'mehrdad'}
|
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
<div className='flex relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
<Email
|
||||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
email={getProfile.data?.data?.user?.email}
|
||||||
<div>
|
isVerified={getProfile.data?.data?.user?.emailVerified}
|
||||||
{t('edit')}
|
|
||||||
</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'>
|
<Phone
|
||||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
phone={getProfile.data?.data?.user?.phone}
|
||||||
<div>
|
|
||||||
{t('edit')}
|
|
||||||
</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>
|
|
||||||
{t('edit')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -112,19 +179,19 @@ const Profile: FC = () => {
|
|||||||
label={t('profile.date_of_birth')}
|
label={t('profile.date_of_birth')}
|
||||||
onChange={() => { }}
|
onChange={() => { }}
|
||||||
placeholder=''
|
placeholder=''
|
||||||
defaulValue='1400-01-01'
|
defaulValue={getProfile.data?.data?.user?.birthDate}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='xl:mt-7 mt-4'>
|
<div className='xl:mt-7 mt-4'>
|
||||||
<Input
|
<Input
|
||||||
label={t('profile.national_code')}
|
label={t('profile.national_code')}
|
||||||
value={'۰۰۱۲۳۴۵۳۴۴'}
|
value={getProfile.data?.data?.user?.nationalCode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7'>
|
{/* <div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7'>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<div>
|
<div>
|
||||||
{t('profile.address')}
|
{t('profile.address')}
|
||||||
@@ -137,42 +204,61 @@ const Profile: FC = () => {
|
|||||||
<div className='rowTwoInput'>
|
<div className='rowTwoInput'>
|
||||||
<Select
|
<Select
|
||||||
label={t('profile.province')}
|
label={t('profile.province')}
|
||||||
items={[
|
items={getProvines.data?.data?.provinces.map((item: ProvinesItemType) => ({ label: item.name, value: item.id }))}
|
||||||
{
|
onChange={(e) => setProvinesId(e.target.value)}
|
||||||
label: 'تهران',
|
|
||||||
value: 'tehran'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
className='bg-white border'
|
className='bg-white border'
|
||||||
|
placeholder={t('select')}
|
||||||
|
value={provinesId}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label={t('profile.city')}
|
label={t('profile.city')}
|
||||||
items={[
|
items={getCities.data?.data?.cities ? getCities.data?.data?.cities.map((item: ProvinesItemType) => ({ label: item.name, value: item.id })) : []}
|
||||||
{
|
placeholder={t('select')}
|
||||||
label: 'تهران',
|
{...formik.getFieldProps('cityId')}
|
||||||
value: 'tehran'
|
error_text={formik.touched.cityId && formik.errors.cityId ? formik.errors.cityId : ''}
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='xl:mt-7 mt-4'>
|
<div className='xl:mt-7 mt-4'>
|
||||||
<Input
|
<Input
|
||||||
label={t('profile.postal_code')}
|
label={t('profile.postal_code')}
|
||||||
value={'۱۲۳۴۵۶۷۸'}
|
{...formik.getFieldProps('postalCode')}
|
||||||
|
error_text={formik.touched.postalCode && formik.errors.postalCode ? formik.errors.postalCode : ''}
|
||||||
|
value={formik.values.postalCode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='xl:mt-7 mt-4'>
|
<div className='xl:mt-7 mt-4'>
|
||||||
<Textarea
|
<Textarea
|
||||||
label={t('profile.address')}
|
label={t('profile.address')}
|
||||||
value={'للورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک استورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است'}
|
defaultValue={formik.values.userAddress}
|
||||||
|
{...formik.getFieldProps('userAddress')}
|
||||||
|
error_text={formik.touched.userAddress && formik.errors.userAddress ? formik.errors.userAddress : ''}
|
||||||
/>
|
/>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div> */}
|
||||||
|
|
||||||
<div className='h-20 xl:hidden'></div>
|
<div className='h-20 xl:hidden'></div>
|
||||||
</div>
|
</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;
|
||||||
|
};
|
||||||
+31
-10
@@ -2,13 +2,13 @@ import { Cards, Money3, MoneyRecive, TickSquare } from 'iconsax-react'
|
|||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Tabs from '../../components/Tabs'
|
import Tabs from '../../components/Tabs'
|
||||||
import Online from './components/Online'
|
import { useGetWalletBalance } from './hooks/useWalletData'
|
||||||
import CardtoCard from './components/CardtoCard'
|
import { NumberFormat } from '../../config/func'
|
||||||
import Sheba from './components/Sheba'
|
|
||||||
|
|
||||||
const Wallet: FC = () => {
|
const Wallet: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const getWalletBalance = useGetWalletBalance()
|
||||||
const [activeTab, setActiveTab] = useState<string>('online')
|
const [activeTab, setActiveTab] = useState<string>('online')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -17,6 +17,15 @@ const Wallet: FC = () => {
|
|||||||
{t('wallet.increese_wallet')}
|
{t('wallet.increese_wallet')}
|
||||||
</div>
|
</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='mt-8 flex gap-6'>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<Tabs
|
<Tabs
|
||||||
@@ -41,19 +50,30 @@ const Wallet: FC = () => {
|
|||||||
onChange={setActiveTab}
|
onChange={setActiveTab}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{/* {
|
||||||
activeTab === 'online' ?
|
activeTab === 'online' ?
|
||||||
<Online />
|
<Online />
|
||||||
: activeTab === 'card' ?
|
: activeTab === 'card' ?
|
||||||
<CardtoCard />
|
<CardtoCard />
|
||||||
: <Sheba />
|
: <Sheba />
|
||||||
}
|
} */}
|
||||||
|
|
||||||
</div>
|
</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='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='bg-white py-10 px-5 h-fit rounded-3xl mt-8'>
|
||||||
<div className='text-sm'>
|
<div className='text-sm'>
|
||||||
{t('ticket.title_hint')}
|
لطفا قبل از شارژ کیف پول نکات زیر را مد نظر قرار دهید.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
@@ -62,7 +82,7 @@ const Wallet: FC = () => {
|
|||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare size={20} color='black' variant='Bold' />
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className='text-description text-xs leading-5'>
|
||||||
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
|
شما می توانید به صورت آنلاین یا کارت به کارت و یا واریز از طریق شماره شبا کیف پول خود را شارژ نمایید.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
||||||
@@ -70,7 +90,7 @@ const Wallet: FC = () => {
|
|||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare size={20} color='black' variant='Bold' />
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className='text-description text-xs leading-5'>
|
||||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
تراکنش های کیف پول شما از طریق بخش تراکنش قابل دسترسی است.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-start gap-2 mt-6'>
|
<div className='flex items-start gap-2 mt-6'>
|
||||||
@@ -78,7 +98,8 @@ const Wallet: FC = () => {
|
|||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare size={20} color='black' variant='Bold' />
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className='text-description text-xs leading-5'>
|
||||||
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
شرایط و مقررات کیف پول داناک در بخش قوانین مقررات درج گردیده، شما با شارژ کیف پول با این قوانین و مقررات موافقت می نمایید.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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;
|
||||||
|
};
|
||||||
+61
-16
@@ -1,25 +1,30 @@
|
|||||||
import { FC } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import Input from '../components/Input'
|
import Input from '../components/Input'
|
||||||
import { ArrowDown2, Element3, HambergerMenu, Wallet } from 'iconsax-react'
|
import { ArrowDown2, CloseCircle, Element3, HambergerMenu, Logout, Wallet } from 'iconsax-react'
|
||||||
import AvatarImage from '../assets/images/Avatar.png'
|
import AvatarImage from '../assets/images/avatar_image.png'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
import { Pages } from '../config/Pages'
|
import { Pages } from '../config/Pages'
|
||||||
import Notifications from '../pages/notification/Notification'
|
import Notifications from '../pages/notification/Notification'
|
||||||
import { useSharedStore } from './store/sharedStore'
|
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 Header: FC = () => {
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
const [popoverKey, setPopoverKey] = useState(0);
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const { setOpenSidebar, openSidebar, hasSubMenu } = useSharedStore()
|
const { setOpenSidebar, openSidebar } = useSharedStore()
|
||||||
|
const { data } = useGetProfile()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPopoverKey((prevKey) => prevKey + 1);
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clx(
|
<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)]'>
|
||||||
'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='min-w-[270px] hidden xl:block'>
|
<div className='min-w-[270px] hidden xl:block'>
|
||||||
<Input
|
<Input
|
||||||
@@ -32,22 +37,62 @@ const Header: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
{/* <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'>
|
<div className='flex xl:gap-6 gap-4 items-center'>
|
||||||
|
<Link to={Pages.services.other}>
|
||||||
<Element3 color='black' className='xl:size-[18px] size-[17px]' />
|
<Element3 color='black' className='xl:size-[18px] size-[17px]' />
|
||||||
<Link to={Pages.wallet}>
|
</Link>
|
||||||
|
<Link className='xl:hidden' to={Pages.wallet}>
|
||||||
<Wallet className='xl:size-[18px] size-[17px]' color='black' />
|
<Wallet className='xl:size-[18px] size-[17px]' color='black' />
|
||||||
</Link>
|
</Link>
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<div className='flex gap-2 items-center'>
|
{
|
||||||
|
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'>
|
<div className='size-6 rounded-full bg-description overflow-hidden'>
|
||||||
<Link to={Pages.profile}>
|
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
|
||||||
<img src={AvatarImage} className='size-full object-cover' />
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='xl:flex hidden gap-1 items-center'>
|
<div className='xl:flex hidden gap-1 items-center'>
|
||||||
<div className='text-xs'>مهرداد مظفری</div>
|
<div className='text-xs'>
|
||||||
|
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
|
||||||
|
</div>
|
||||||
<ArrowDown2 size={14} color='#8C90A3' />
|
<ArrowDown2 size={14} color='#8C90A3' />
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+11
-1
@@ -16,11 +16,13 @@ import TicketSubMenu from './components/TicketSubMenu'
|
|||||||
import UsersSubMenu from './components/UsersSubMenu'
|
import UsersSubMenu from './components/UsersSubMenu'
|
||||||
import LearningSubMenu from './components/LearningSubMenu'
|
import LearningSubMenu from './components/LearningSubMenu'
|
||||||
import BlogSubMenu from './components/BlogSubMenu'
|
import BlogSubMenu from './components/BlogSubMenu'
|
||||||
|
import { useGetProfile } from '../pages/profile/hooks/useProfileData'
|
||||||
const SideBar: FC = () => {
|
const SideBar: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const { openSidebar, setOpenSidebar, hasSubMenu, setSubMenuName, setSubtMenu, subMenuName } = useSharedStore()
|
const { openSidebar, setOpenSidebar, hasSubMenu, setSubMenuName, setSubtMenu, subMenuName } = useSharedStore()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const { data: profile } = useGetProfile()
|
||||||
const isActive = (name: string) => {
|
const isActive = (name: string) => {
|
||||||
return location.pathname.includes(name)
|
return location.pathname.includes(name)
|
||||||
}
|
}
|
||||||
@@ -158,12 +160,17 @@ const SideBar: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
profile?.data?.user?.roles.some((role: { name: string }) => role.name === 'super_admin') &&
|
||||||
<div className={clx(
|
<div className={clx(
|
||||||
'mt-10 px-12 text-header text-sm font-normal',
|
'mt-10 px-12 text-header text-sm font-normal',
|
||||||
hasSubMenu && 'px-2 text-center'
|
hasSubMenu && 'px-2 text-center'
|
||||||
)}>
|
)}>
|
||||||
{t('sidebar.other')}
|
{t('sidebar.other')}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className='text-xs text-[#8C90A3]'>
|
<div className='text-xs text-[#8C90A3]'>
|
||||||
@@ -186,13 +193,16 @@ const SideBar: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
profile?.data?.user?.roles?.some((role: { name: string }) => role.name === 'super_admin') &&
|
||||||
<div className={clx(
|
<div className={clx(
|
||||||
'mt-10 px-12 text-header text-sm font-normal',
|
'mt-10 px-12 text-header text-sm font-normal',
|
||||||
hasSubMenu && 'px-2 text-center text-xs'
|
hasSubMenu && 'px-2 text-center text-xs'
|
||||||
)}>
|
)}>
|
||||||
{t('sidebar.mnage_content')}
|
{t('sidebar.mnage_content')}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
<div className='text-xs text-[#8C90A3]'>
|
<div className='text-xs text-[#8C90A3]'>
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
|
|||||||
Reference in New Issue
Block a user