update profile
This commit is contained in:
@@ -6,55 +6,161 @@ import ComboBox from '@/components/combobox/Combobox';
|
||||
import InputField from '@/components/input/InputField';
|
||||
import { PORFILE_EDIT_PAGE_ELEMENT } from '@/enums';
|
||||
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 Image from 'next/image';
|
||||
import React, { ChangeEvent, useState } from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import Calendar from '@/components/ui/calendar';
|
||||
import { getDateLib } from "react-day-picker/persian";
|
||||
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
|
||||
|
||||
function ProfileIndex({ }: Props) {
|
||||
const [fullname, setFullName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const [dateOfBirth, setDateOfBirth] = React.useState<Date | undefined>(new Date());
|
||||
const [selectedGender, setSelectedGender] = useState("");
|
||||
const [showCalendar, setShowCalendar] = useState(false);
|
||||
|
||||
const { data, isPending } = useGetProfile();
|
||||
const { mutate: updateProfile, isPending: isUpdating } = useUpdateProfile();
|
||||
const [showCalendar, setShowCalendar] = React.useState(false);
|
||||
const dateLib = getDateLib()
|
||||
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) => {
|
||||
setSelectedGender(() => String(index));
|
||||
formik.setFieldValue('gender', index === 0);
|
||||
}
|
||||
|
||||
// const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||
|
||||
// const { mutate, data, isPending } = useProfile();
|
||||
|
||||
// useEffect(() => {
|
||||
// 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)
|
||||
// }
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className='h-full noscrollbar overflow-y-auto flex flex-col items-center justify-center'>
|
||||
<p className='text-sm2 text-disabled-text'>در حال بارگذاری...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -64,16 +170,13 @@ function ProfileIndex({ }: Props) {
|
||||
<h1 className='text-sm2 place-self-center font-medium'>ویرایش اطلاعات</h1>
|
||||
</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="justify-self-center w-fit relative text-center pb-10"
|
||||
>
|
||||
<button>
|
||||
<div className="justify-self-center w-fit relative text-center pb-10">
|
||||
<button type="button">
|
||||
<Image
|
||||
src={'/assets/images/user-avatar.png'}
|
||||
className='rounded-full'
|
||||
|
||||
alt='user avatar'
|
||||
width={96}
|
||||
height={96}
|
||||
@@ -90,39 +193,37 @@ function ProfileIndex({ }: Props) {
|
||||
|
||||
<div className="grid gap-6 mt-6 bg-inherit">
|
||||
<InputField
|
||||
onChange={onChange}
|
||||
name="firstName"
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
className='bg-inherit'
|
||||
labelText='نام و نام خانوادگی'
|
||||
htmlFor={PORFILE_EDIT_PAGE_ELEMENT.INPUT_FULLNAME}
|
||||
value={fullname}
|
||||
labelText='نام'
|
||||
htmlFor="firstName"
|
||||
value={formik.values.firstName}
|
||||
valid={!(formik.touched.firstName && formik.errors.firstName)}
|
||||
aria-errormessage={formik.touched.firstName && formik.errors.firstName ? formik.errors.firstName : undefined}
|
||||
/>
|
||||
<InputField
|
||||
onChange={onChange}
|
||||
dir='ltr'
|
||||
type='email'
|
||||
name="lastName"
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
className='bg-inherit'
|
||||
labelText='ایمیل'
|
||||
htmlFor={PORFILE_EDIT_PAGE_ELEMENT.INPUT_EMAIL}
|
||||
value={email}
|
||||
/>
|
||||
<InputField
|
||||
onChange={onChange}
|
||||
dir='ltr'
|
||||
type='number'
|
||||
className='bg-inherit'
|
||||
labelText='شماره همراه'
|
||||
htmlFor={PORFILE_EDIT_PAGE_ELEMENT.INPUT_PHONE}
|
||||
value={phoneNumber}
|
||||
labelText='نام خانوادگی'
|
||||
htmlFor="lastName"
|
||||
value={formik.values.lastName}
|
||||
valid={!(formik.touched.lastName && formik.errors.lastName)}
|
||||
aria-errormessage={formik.touched.lastName && formik.errors.lastName ? formik.errors.lastName : undefined}
|
||||
/>
|
||||
<Popover open={showCalendar} onOpenChange={setShowCalendar}>
|
||||
<PopoverTrigger asChild>
|
||||
<ShadButton
|
||||
type="button"
|
||||
variant="outline"
|
||||
id="date"
|
||||
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'>
|
||||
{dateOfBirth ? dateLib.format(dateOfBirth, "yyyy/MM/dd") : "انتخاب کنید"}
|
||||
{formatDateForDisplay(formik.values.birthDate)}
|
||||
</div>
|
||||
<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>
|
||||
@@ -132,26 +233,24 @@ function ProfileIndex({ }: Props) {
|
||||
<Calendar
|
||||
className='bg-white dark:bg-background z-20 min-w-64 rounded-lg'
|
||||
mode="single"
|
||||
defaultMonth={dateOfBirth}
|
||||
selected={dateOfBirth}
|
||||
defaultMonth={formik.values.birthDate || undefined}
|
||||
selected={formik.values.birthDate || undefined}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(date) => {
|
||||
setDateOfBirth(date)
|
||||
setShowCalendar(false)
|
||||
formik.setFieldValue('birthDate', date || undefined);
|
||||
setShowCalendar(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
|
||||
<div className='relative'>
|
||||
<ComboBox
|
||||
searchable={false}
|
||||
title=""
|
||||
options={[{ id: '0', title: 'آقا', label: 'آقا' }, { id: '1', title: 'خانوم', label: 'خانوم' }]}
|
||||
id={PORFILE_EDIT_PAGE_ELEMENT.INPUT_GENDER}
|
||||
selectedId={selectedGender}
|
||||
selectedId={selectedGenderId}
|
||||
onSelectionChange={changeGender} />
|
||||
<span className='absolute text-xs top-0 -translate-y-2 right-2 bg-container px-2'>جنسیت</span>
|
||||
</div>
|
||||
@@ -159,15 +258,18 @@ function ProfileIndex({ }: Props) {
|
||||
</div>
|
||||
|
||||
<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
|
||||
type="button"
|
||||
className='bg-neutral-200! dark:bg-neutral-600! text-disabled-text!'
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
انصراف
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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 { getToken } from "@/lib/api/func";
|
||||
|
||||
@@ -10,3 +10,9 @@ export const useGetProfile = () => {
|
||||
enabled: !!getToken(),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateProfile = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.updateProfile,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { api } from "@/config/axios";
|
||||
import { GetProfileResponse } from "../types/Types";
|
||||
import { GetProfileResponse, UpdateProfileType } from "../types/Types";
|
||||
|
||||
export const getProfile = async (): Promise<GetProfileResponse> => {
|
||||
const { data } = await api.get<GetProfileResponse>("/public/user/me");
|
||||
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;
|
||||
data: UserProfile;
|
||||
}
|
||||
|
||||
export interface UpdateProfileType {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
birthDate?: string;
|
||||
marriageDate?: string;
|
||||
gender?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user