update profile
This commit is contained in:
@@ -6,55 +6,161 @@ import ComboBox from '@/components/combobox/Combobox';
|
|||||||
import InputField from '@/components/input/InputField';
|
import InputField from '@/components/input/InputField';
|
||||||
import { PORFILE_EDIT_PAGE_ELEMENT } from '@/enums';
|
import { PORFILE_EDIT_PAGE_ELEMENT } from '@/enums';
|
||||||
import { Popover, PopoverTrigger, PopoverContent } from '@radix-ui/react-popover';
|
import { Popover, PopoverTrigger, PopoverContent } from '@radix-ui/react-popover';
|
||||||
// import { useProfile } from '@/hooks/auth/useProfile';
|
|
||||||
// import { useAuthStore } from '@/zustand/authStore';
|
|
||||||
import { Calendar2, Camera } from 'iconsax-react';
|
import { Calendar2, Camera } from 'iconsax-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import React, { ChangeEvent, useState } from 'react'
|
import React, { useEffect, useMemo } from 'react'
|
||||||
import Calendar from '@/components/ui/calendar';
|
import Calendar from '@/components/ui/calendar';
|
||||||
import { getDateLib } from "react-day-picker/persian";
|
import { getDateLib } from "react-day-picker/persian";
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useGetProfile, useUpdateProfile } from '../hooks/userProfileData';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { UpdateProfileType } from '../types/Types';
|
||||||
|
import { toast } from '@/components/Toast';
|
||||||
|
import { extractErrorMessage } from '@/lib/func';
|
||||||
|
|
||||||
type Props = object
|
type Props = object
|
||||||
|
|
||||||
function ProfileIndex({ }: Props) {
|
function ProfileIndex({ }: Props) {
|
||||||
const [fullname, setFullName] = useState("");
|
|
||||||
const [email, setEmail] = useState("");
|
const { data, isPending } = useGetProfile();
|
||||||
const [phoneNumber, setPhoneNumber] = useState("");
|
const { mutate: updateProfile, isPending: isUpdating } = useUpdateProfile();
|
||||||
const [dateOfBirth, setDateOfBirth] = React.useState<Date | undefined>(new Date());
|
const [showCalendar, setShowCalendar] = React.useState(false);
|
||||||
const [selectedGender, setSelectedGender] = useState("");
|
|
||||||
const [showCalendar, setShowCalendar] = useState(false);
|
|
||||||
const dateLib = getDateLib()
|
const dateLib = getDateLib()
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const userData = data?.data;
|
||||||
|
|
||||||
|
// تابع برای بررسی معتبر بودن Date
|
||||||
|
const isValidDate = (date: Date | null | undefined): date is Date => {
|
||||||
|
return date instanceof Date && !isNaN(date.getTime());
|
||||||
|
};
|
||||||
|
|
||||||
|
// تابع برای تبدیل string تاریخ به Date بدون مشکل timezone
|
||||||
|
const parseLocalDate = (dateString: string): Date | undefined => {
|
||||||
|
try {
|
||||||
|
if (!dateString) return undefined;
|
||||||
|
|
||||||
|
// اگر تاریخ به فرمت ISO یا کامل است، ابتدا به Date تبدیل میکنیم
|
||||||
|
const date = new Date(dateString);
|
||||||
|
if (isValidDate(date)) {
|
||||||
|
// برای جلوگیری از مشکل timezone، فقط سال، ماه و روز را میگیریم
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth();
|
||||||
|
const day = date.getDate();
|
||||||
|
return new Date(year, month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر فرمت ISO کار نکرد، سعی میکنیم فرمت YYYY-MM-DD را parse کنیم
|
||||||
|
const parts = dateString.split('T')[0].split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
const [year, month, day] = parts.map(Number);
|
||||||
|
if (!isNaN(year) && !isNaN(month) && !isNaN(day)) {
|
||||||
|
const parsedDate = new Date(year, month - 1, day);
|
||||||
|
if (isValidDate(parsedDate)) {
|
||||||
|
return parsedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// تابع برای تبدیل Date به string تاریخ
|
||||||
|
const formatDateToString = (date: Date): string => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// تابع برای format کردن تاریخ با dateLib
|
||||||
|
const formatDateForDisplay = (date: Date | null | undefined): string => {
|
||||||
|
if (!date || !isValidDate(date)) {
|
||||||
|
return "انتخاب کنید";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return dateLib.format(date, "yyyy/MM/dd");
|
||||||
|
} catch {
|
||||||
|
return "انتخاب کنید";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type FormValues = {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
birthDate?: Date | null;
|
||||||
|
gender?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema: Yup.ObjectSchema<FormValues> = Yup.object({
|
||||||
|
firstName: Yup.string().required('نام اجباری است'),
|
||||||
|
lastName: Yup.string().required('نام خانوادگی اجباری است'),
|
||||||
|
}) as Yup.ObjectSchema<FormValues>;
|
||||||
|
|
||||||
|
const formik = useFormik<FormValues>({
|
||||||
|
initialValues: {
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
birthDate: undefined,
|
||||||
|
gender: undefined,
|
||||||
|
},
|
||||||
|
validationSchema,
|
||||||
|
enableReinitialize: true,
|
||||||
|
onSubmit: (values) => {
|
||||||
|
const { birthDate, ...submitValues } = values;
|
||||||
|
const payload: UpdateProfileType = {
|
||||||
|
firstName: submitValues.firstName,
|
||||||
|
lastName: submitValues.lastName,
|
||||||
|
birthDate: birthDate ? formatDateToString(birthDate) : undefined,
|
||||||
|
gender: submitValues.gender,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateProfile(payload, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast('اطلاعات با موفقیت بهروزرسانی شد', 'success');
|
||||||
|
router.back();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast(extractErrorMessage(error), 'error');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userData) {
|
||||||
|
const birthDate = userData.birthDate ? parseLocalDate(userData.birthDate) : undefined;
|
||||||
|
const values: FormValues = {
|
||||||
|
firstName: userData.firstName || '',
|
||||||
|
lastName: userData.lastName || '',
|
||||||
|
birthDate: birthDate,
|
||||||
|
gender: userData.gender !== undefined ? userData.gender : undefined,
|
||||||
|
};
|
||||||
|
formik.setValues(values);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [userData]);
|
||||||
|
|
||||||
|
const selectedGenderId = useMemo(() => {
|
||||||
|
if (formik.values.gender === true) return '0';
|
||||||
|
if (formik.values.gender === false) return '1';
|
||||||
|
return '';
|
||||||
|
}, [formik.values.gender]);
|
||||||
|
|
||||||
const changeGender = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
|
const changeGender = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
|
||||||
setSelectedGender(() => String(index));
|
formik.setFieldValue('gender', index === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
if (isPending) {
|
||||||
|
return (
|
||||||
// const { mutate, data, isPending } = useProfile();
|
<div className='h-full noscrollbar overflow-y-auto flex flex-col items-center justify-center'>
|
||||||
|
<p className='text-sm2 text-disabled-text'>در حال بارگذاری...</p>
|
||||||
// useEffect(() => {
|
</div>
|
||||||
// if (isAuthenticated) {
|
);
|
||||||
// mutate();
|
|
||||||
// }
|
|
||||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
// }, [isAuthenticated])
|
|
||||||
|
|
||||||
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (e.target.name == PORFILE_EDIT_PAGE_ELEMENT.INPUT_FULLNAME) {
|
|
||||||
setFullName(() => e.target.value)
|
|
||||||
}
|
|
||||||
else if (e.target.name == PORFILE_EDIT_PAGE_ELEMENT.INPUT_EMAIL) {
|
|
||||||
setEmail(() => e.target.value)
|
|
||||||
}
|
|
||||||
else if (e.target.name == PORFILE_EDIT_PAGE_ELEMENT.INPUT_PHONE) {
|
|
||||||
setPhoneNumber(() => e.target.value)
|
|
||||||
}
|
|
||||||
// else if (e.target.name == PORFILE_EDIT_PAGE_ELEMENT.INPUT_DOB) {
|
|
||||||
// setDateOfBirth(() => e.target.value)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -64,16 +170,13 @@ function ProfileIndex({ }: Props) {
|
|||||||
<h1 className='text-sm2 place-self-center font-medium'>ویرایش اطلاعات</h1>
|
<h1 className='text-sm2 place-self-center font-medium'>ویرایش اطلاعات</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 flex-1 bg-container rounded-container w-full box-shadow-normal py-6 px-4 flex flex-col justify-between">
|
<form onSubmit={formik.handleSubmit} className="mt-8 flex-1 bg-container rounded-container w-full box-shadow-normal py-6 px-4 flex flex-col justify-between">
|
||||||
|
|
||||||
<div className="bg-inherit">
|
<div className="bg-inherit">
|
||||||
<div className="justify-self-center w-fit relative text-center pb-10"
|
<div className="justify-self-center w-fit relative text-center pb-10">
|
||||||
>
|
<button type="button">
|
||||||
<button>
|
|
||||||
<Image
|
<Image
|
||||||
src={'/assets/images/user-avatar.png'}
|
src={'/assets/images/user-avatar.png'}
|
||||||
className='rounded-full'
|
className='rounded-full'
|
||||||
|
|
||||||
alt='user avatar'
|
alt='user avatar'
|
||||||
width={96}
|
width={96}
|
||||||
height={96}
|
height={96}
|
||||||
@@ -90,39 +193,37 @@ function ProfileIndex({ }: Props) {
|
|||||||
|
|
||||||
<div className="grid gap-6 mt-6 bg-inherit">
|
<div className="grid gap-6 mt-6 bg-inherit">
|
||||||
<InputField
|
<InputField
|
||||||
onChange={onChange}
|
name="firstName"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
className='bg-inherit'
|
className='bg-inherit'
|
||||||
labelText='نام و نام خانوادگی'
|
labelText='نام'
|
||||||
htmlFor={PORFILE_EDIT_PAGE_ELEMENT.INPUT_FULLNAME}
|
htmlFor="firstName"
|
||||||
value={fullname}
|
value={formik.values.firstName}
|
||||||
|
valid={!(formik.touched.firstName && formik.errors.firstName)}
|
||||||
|
aria-errormessage={formik.touched.firstName && formik.errors.firstName ? formik.errors.firstName : undefined}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
onChange={onChange}
|
name="lastName"
|
||||||
dir='ltr'
|
onChange={formik.handleChange}
|
||||||
type='email'
|
onBlur={formik.handleBlur}
|
||||||
className='bg-inherit'
|
className='bg-inherit'
|
||||||
labelText='ایمیل'
|
labelText='نام خانوادگی'
|
||||||
htmlFor={PORFILE_EDIT_PAGE_ELEMENT.INPUT_EMAIL}
|
htmlFor="lastName"
|
||||||
value={email}
|
value={formik.values.lastName}
|
||||||
/>
|
valid={!(formik.touched.lastName && formik.errors.lastName)}
|
||||||
<InputField
|
aria-errormessage={formik.touched.lastName && formik.errors.lastName ? formik.errors.lastName : undefined}
|
||||||
onChange={onChange}
|
|
||||||
dir='ltr'
|
|
||||||
type='number'
|
|
||||||
className='bg-inherit'
|
|
||||||
labelText='شماره همراه'
|
|
||||||
htmlFor={PORFILE_EDIT_PAGE_ELEMENT.INPUT_PHONE}
|
|
||||||
value={phoneNumber}
|
|
||||||
/>
|
/>
|
||||||
<Popover open={showCalendar} onOpenChange={setShowCalendar}>
|
<Popover open={showCalendar} onOpenChange={setShowCalendar}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<ShadButton
|
<ShadButton
|
||||||
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
id="date"
|
id="date"
|
||||||
className="w-full bg-container text-border dark:text-border justify-between font-normal relative h-11 rounded-normal hover:bg-white"
|
className="w-full bg-container text-border dark:text-border justify-between font-normal relative h-11 rounded-normal hover:bg-white"
|
||||||
>
|
>
|
||||||
<div className='text-foreground'>
|
<div className='text-foreground'>
|
||||||
{dateOfBirth ? dateLib.format(dateOfBirth, "yyyy/MM/dd") : "انتخاب کنید"}
|
{formatDateForDisplay(formik.values.birthDate)}
|
||||||
</div>
|
</div>
|
||||||
<Calendar2 className='stroke-disabled2 dark:stroke-foreground' size={24} />
|
<Calendar2 className='stroke-disabled2 dark:stroke-foreground' size={24} />
|
||||||
<span className='absolute text-foreground text-xs top-0 -translate-y-2 right-2 bg-container px-2'>تاریخ تولد</span>
|
<span className='absolute text-foreground text-xs top-0 -translate-y-2 right-2 bg-container px-2'>تاریخ تولد</span>
|
||||||
@@ -132,26 +233,24 @@ function ProfileIndex({ }: Props) {
|
|||||||
<Calendar
|
<Calendar
|
||||||
className='bg-white dark:bg-background z-20 min-w-64 rounded-lg'
|
className='bg-white dark:bg-background z-20 min-w-64 rounded-lg'
|
||||||
mode="single"
|
mode="single"
|
||||||
defaultMonth={dateOfBirth}
|
defaultMonth={formik.values.birthDate || undefined}
|
||||||
selected={dateOfBirth}
|
selected={formik.values.birthDate || undefined}
|
||||||
captionLayout="dropdown"
|
captionLayout="dropdown"
|
||||||
onSelect={(date) => {
|
onSelect={(date) => {
|
||||||
setDateOfBirth(date)
|
formik.setFieldValue('birthDate', date || undefined);
|
||||||
setShowCalendar(false)
|
setShowCalendar(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
searchable={false}
|
searchable={false}
|
||||||
title=""
|
title=""
|
||||||
options={[{ id: '0', title: 'آقا', label: 'آقا' }, { id: '1', title: 'خانوم', label: 'خانوم' }]}
|
options={[{ id: '0', title: 'آقا', label: 'آقا' }, { id: '1', title: 'خانوم', label: 'خانوم' }]}
|
||||||
id={PORFILE_EDIT_PAGE_ELEMENT.INPUT_GENDER}
|
id={PORFILE_EDIT_PAGE_ELEMENT.INPUT_GENDER}
|
||||||
selectedId={selectedGender}
|
selectedId={selectedGenderId}
|
||||||
onSelectionChange={changeGender} />
|
onSelectionChange={changeGender} />
|
||||||
<span className='absolute text-xs top-0 -translate-y-2 right-2 bg-container px-2'>جنسیت</span>
|
<span className='absolute text-xs top-0 -translate-y-2 right-2 bg-container px-2'>جنسیت</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -159,15 +258,18 @@ function ProfileIndex({ }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='w-full text-center mt-16 grid grid-cols-2 gap-4'>
|
<div className='w-full text-center mt-16 grid grid-cols-2 gap-4'>
|
||||||
<Button>ذخیره</Button>
|
<Button type="submit" disabled={isUpdating || !formik.isValid}>
|
||||||
|
{isUpdating ? 'در حال ذخیره...' : 'ذخیره'}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
className='bg-neutral-200! dark:bg-neutral-600! text-disabled-text!'
|
className='bg-neutral-200! dark:bg-neutral-600! text-disabled-text!'
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
>
|
>
|
||||||
انصراف
|
انصراف
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import * as api from "../service/ProfileService";
|
import * as api from "../service/ProfileService";
|
||||||
import { getToken } from "@/lib/api/func";
|
import { getToken } from "@/lib/api/func";
|
||||||
|
|
||||||
@@ -10,3 +10,9 @@ export const useGetProfile = () => {
|
|||||||
enabled: !!getToken(),
|
enabled: !!getToken(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdateProfile = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.updateProfile,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { api } from "@/config/axios";
|
import { api } from "@/config/axios";
|
||||||
import { GetProfileResponse } from "../types/Types";
|
import { GetProfileResponse, UpdateProfileType } from "../types/Types";
|
||||||
|
|
||||||
export const getProfile = async (): Promise<GetProfileResponse> => {
|
export const getProfile = async (): Promise<GetProfileResponse> => {
|
||||||
const { data } = await api.get<GetProfileResponse>("/public/user/me");
|
const { data } = await api.get<GetProfileResponse>("/public/user/me");
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateProfile = async (params: UpdateProfileType) => {
|
||||||
|
const { data } = await api.patch("/public/user/update", params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -21,3 +21,11 @@ export interface GetProfileResponse {
|
|||||||
success: boolean;
|
success: boolean;
|
||||||
data: UserProfile;
|
data: UserProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateProfileType {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
birthDate?: string;
|
||||||
|
marriageDate?: string;
|
||||||
|
gender?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user