@@ -0,0 +1,168 @@
|
||||
import { useState, type FC } from 'react'
|
||||
import Input from '@/components/Input'
|
||||
import Textarea from '@/components/Textarea'
|
||||
import UploadBox from '@/components/UploadBox'
|
||||
import PresignedImage from '@/components/PresignedImage'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import Button from '@/components/Button'
|
||||
import Error from '@/components/Error'
|
||||
import { useGetMe, useUpdateProfile } from '@/pages/user/hooks/useUserData'
|
||||
import { useSingleUpload } from '@/pages/uploader/hooks/useUploader'
|
||||
import { toast } from '@/shared/toast'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
import type { UpdateProfileType } from '@/pages/user/types/Types'
|
||||
import { Edit } from 'iconsax-react'
|
||||
|
||||
const Profile: FC = () => {
|
||||
const { data: user, isLoading } = useGetMe()
|
||||
const updateProfile = useUpdateProfile()
|
||||
const { mutate: upload, isPending: isUploading } = useSingleUpload()
|
||||
const [file, setFile] = useState<File>()
|
||||
|
||||
const handleSave = (values: UpdateProfileType) => {
|
||||
updateProfile.mutate(
|
||||
{
|
||||
firstName: values.firstName.trim(),
|
||||
lastName: values.lastName.trim(),
|
||||
address: values.address?.trim() || undefined,
|
||||
...(values.avatarUrl ? { avatarUrl: values.avatarUrl } : {}),
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
toast('پروفایل با موفقیت ویرایش شد', 'success')
|
||||
},
|
||||
onError(error) {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const formik = useFormik<UpdateProfileType>({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
firstName: user?.firstName ?? '',
|
||||
lastName: user?.lastName ?? '',
|
||||
address: user?.addresse ?? '',
|
||||
avatarUrl: user?.avatarUrl ?? undefined,
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
firstName: Yup.string()
|
||||
.trim()
|
||||
.required('نام اجباری است.'),
|
||||
lastName: Yup.string()
|
||||
.trim()
|
||||
.required('نام خانوادگی اجباری است.'),
|
||||
}),
|
||||
onSubmit(values) {
|
||||
if (file) {
|
||||
upload(file, {
|
||||
onSuccess(data) {
|
||||
handleSave({
|
||||
...values,
|
||||
avatarUrl: data?.data?.key,
|
||||
})
|
||||
},
|
||||
onError(error) {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
},
|
||||
})
|
||||
} else {
|
||||
handleSave(values)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='mt-5 flex h-64 items-center justify-center'>
|
||||
<div className='text-lg'>در حال بارگذاری...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-5'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<h1 className='text-lg font-light'>پروفایل</h1>
|
||||
<Button
|
||||
className='w-fit px-6'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={updateProfile.isPending || isUploading}
|
||||
>
|
||||
<div className='flex gap-1.5'>
|
||||
<Edit size={18} color='black' />
|
||||
<div className='text-[13px] font-light'>ذخیره تغییرات</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex-1 rounded-3xl bg-white p-6'>
|
||||
{formik.values.avatarUrl && !file && (
|
||||
<PresignedImage
|
||||
src={formik.values.avatarUrl}
|
||||
className='mb-4 h-20 w-20 rounded-full object-cover'
|
||||
alt='آواتار'
|
||||
/>
|
||||
)}
|
||||
|
||||
<UploadBox
|
||||
label='آواتار (اختیاری)'
|
||||
onChange={(files) => setFile(files[0])}
|
||||
/>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<div>
|
||||
<Input
|
||||
label='نام'
|
||||
placeholder='نام خود را وارد کنید'
|
||||
name='firstName'
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
value={formik.values.firstName}
|
||||
/>
|
||||
{formik.touched.firstName && formik.errors.firstName && (
|
||||
<Error errorText={formik.errors.firstName} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Input
|
||||
label='نام خانوادگی'
|
||||
placeholder='نام خانوادگی خود را وارد کنید'
|
||||
name='lastName'
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
value={formik.values.lastName}
|
||||
/>
|
||||
{formik.touched.lastName && formik.errors.lastName && (
|
||||
<Error errorText={formik.errors.lastName} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label='شماره موبایل'
|
||||
value={user?.phone ? `0${user.phone}` : ''}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Textarea
|
||||
label='آدرس'
|
||||
placeholder='آدرس خود را وارد کنید'
|
||||
name='address'
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
value={formik.values.address ?? ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Profile
|
||||
@@ -1,21 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import axios from "@/config/axios";
|
||||
|
||||
export interface ProfileData {
|
||||
user: {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
profilePic?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const useGetProfile = () => {
|
||||
return useQuery({
|
||||
queryKey: ["profile"],
|
||||
queryFn: async (): Promise<{ data: ProfileData }> => {
|
||||
const response = await axios.get("/profile");
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -19,5 +19,6 @@ export type GetMeResponseType = BaseResponse<UserMeType>;
|
||||
export type UpdateProfileType = {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
address?: string;
|
||||
avatarUrl?: string;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ import LearningList from '@/pages/learning/List'
|
||||
import LearningDetail from '@/pages/learning/Detail'
|
||||
import InvoiceDetail from '@/pages/invoice/Detail'
|
||||
import PayInvoice from '@/pages/payment/PayInvoice'
|
||||
import Profile from '@/pages/profile/Profile'
|
||||
|
||||
const MainRouter: FC = () => {
|
||||
return (
|
||||
@@ -26,6 +27,7 @@ const MainRouter: FC = () => {
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path={Paths.home} element={<Home />} />
|
||||
<Route path={Paths.profile} element={<Profile />} />
|
||||
<Route path={Paths.myRequests} element={<MyRequests />} />
|
||||
<Route path={Paths.order.myOrders} element={<MyOrders />} />
|
||||
<Route path={Paths.proformaInvoice} element={<ProformaInvoice />} />
|
||||
|
||||
+19
-29
@@ -1,12 +1,14 @@
|
||||
import { type FC } from 'react'
|
||||
import Input from '@/components/Input'
|
||||
import PresignedImage from '@/components/PresignedImage'
|
||||
import UserAvatar from '@/components/UserAvatar'
|
||||
import { HambergerMenu, Wallet } from 'iconsax-react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import Notifications from '@/pages/notification/Notification'
|
||||
import { useSharedStore } from './store/useSharedStore'
|
||||
import { t } from '@/locale'
|
||||
import { useGetMe } from '@/pages/user/hooks/useUserData'
|
||||
import { NumberFormat } from '@/config/func'
|
||||
import { Paths } from '@/config/Paths'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
@@ -14,7 +16,6 @@ const Header: FC = () => {
|
||||
const { setOpenSidebar, openSidebar } = useSharedStore()
|
||||
|
||||
const displayName = [data?.firstName, data?.lastName].filter(Boolean).join(' ').trim()
|
||||
const initials = `${String(data?.firstName ?? '').charAt(0)}${String(data?.lastName ?? '').charAt(0)}`
|
||||
|
||||
return (
|
||||
<div className='fixed z-10 left-[var(--layout-frame)] right-[var(--layout-frame)] top-[var(--layout-frame)] flex h-[var(--layout-header-height)] items-center justify-between rounded-[32px] bg-white px-6 xl:left-auto xl:right-[calc(var(--layout-frame)+var(--layout-main-offset))] xl:h-[var(--layout-header-height-xl)] xl:w-[calc(100%-(var(--layout-frame)*2)-var(--layout-main-offset))]'>
|
||||
@@ -29,15 +30,7 @@ const Header: FC = () => {
|
||||
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
|
||||
<HambergerMenu size={24} color='black' />
|
||||
</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'>
|
||||
{/* <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>
|
||||
<Link className='hidden xl:block' to={Pages.wallet}> */}
|
||||
<div className='flex items-center h-8 pl-2 rounded-full bg-[#EEF0F7]'>
|
||||
<div className='px-3 text-xs'>
|
||||
{NumberFormat(data?.maxCredit) + ' ' + t('rial')}
|
||||
@@ -46,29 +39,26 @@ const Header: FC = () => {
|
||||
<Wallet className='xl:size-[18px] size-[17px]' color='black' />
|
||||
</div>
|
||||
</div>
|
||||
{/* </Link> */}
|
||||
<Notifications />
|
||||
{displayName && (
|
||||
<div className='flex gap-2 items-center'>
|
||||
<div className='size-6 rounded-full bg-description overflow-hidden flex items-center justify-center text-white text-xs font-medium'>
|
||||
{data?.avatarUrl ? (
|
||||
<PresignedImage
|
||||
src={data.avatarUrl}
|
||||
className='size-full object-cover'
|
||||
alt=''
|
||||
/>
|
||||
) : (
|
||||
initials
|
||||
)}
|
||||
</div>
|
||||
<div className='xl:block hidden text-xs'>
|
||||
{displayName}
|
||||
</div>
|
||||
</div>
|
||||
{data && (
|
||||
<Link to={Paths.profile} className='flex gap-2 items-center'>
|
||||
<UserAvatar
|
||||
src={data.avatarUrl}
|
||||
firstName={data.firstName}
|
||||
lastName={data.lastName}
|
||||
className='size-6'
|
||||
textClassName='text-xs'
|
||||
/>
|
||||
{displayName && (
|
||||
<div className='xl:block hidden text-xs'>
|
||||
{displayName}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
export default Header
|
||||
|
||||
Reference in New Issue
Block a user