prile form:

This commit is contained in:
hamid zarghami
2025-09-16 10:46:02 +03:30
parent ab29dc26dd
commit 87bc7866fa
4 changed files with 229 additions and 113 deletions
+17 -1
View File
@@ -1,6 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import * as api from "../service/Service";
import { useSharedStore } from "@/share/store/sharedStore";
import { UpdateProfileRequest } from "../types/ProfileTypes";
export const useGetProfile = () => {
const { isLogin } = useSharedStore();
@@ -10,3 +11,18 @@ export const useGetProfile = () => {
enabled: isLogin,
});
};
export const useUpdateProfile = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (profileData: UpdateProfileRequest) =>
api.updateProfile(profileData),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["profile"] });
},
onError: (error: any) => {
console.error("خطا در به‌روزرسانی پروفایل:", error);
},
});
};
+140 -112
View File
@@ -1,40 +1,94 @@
'use client'
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as yup from 'yup'
import { Button } from '@/components/ui/button'
import Input from '@/components/Input'
import { Edit2 } from 'lucide-react'
import Layout from '@/hoc/Layout'
import { Heart, ShoppingCart } from 'iconsax-react'
import Menu from './components/Menu'
import { useGetProfile, useUpdateProfile } from './hooks/useProfileData'
// Schema validation
const profileSchema = yup.object({
fullName: yup.string().required('نام و نام خانوادگی الزامی است'),
phoneNumber: yup
.string()
.required('شماره تماس الزامی است')
.matches(/^09\d{9}$/, 'شماره تماس باید با 09 شروع شود و 11 رقم باشد'),
dateOfBirth: yup.string().required('تاریخ تولد الزامی است'),
nationalCode: yup
.string()
.required('کد ملی الزامی است')
.matches(/^\d{10}$/, 'کد ملی باید 10 رقم باشد'),
address: yup.string().required('آدرس الزامی است'),
})
const ProfilePage = () => {
const [userInfo, setUserInfo] = useState({
name: 'حسین دهقانی',
email: 'sondos@gmail.com',
phone: '09010901789',
nationalCode: '052093452',
birthDate: '1365/5/8',
postalCode: '1235478951',
province: 'مرکزی',
city: 'اراک',
address: 'خیابان دانشگاه رودوی بیمارستان خوانساری ساختمان مهر طبقه سوم واحد2',
district: '58'
})
const { data: profile, isLoading } = useGetProfile()
const updateProfileMutation = useUpdateProfile()
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (field: string, value: string) => {
setUserInfo(prev => ({
...prev,
[field]: value
}))
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset,
setValue,
} = useForm({
resolver: yupResolver(profileSchema),
mode: 'onChange',
defaultValues: {
fullName: '',
phoneNumber: '',
dateOfBirth: '',
nationalCode: '',
address: '',
},
})
// Set form values when profile data is loaded
useEffect(() => {
if (profile?.results?.info) {
const userInfo = profile.results.info
setValue('fullName', userInfo.fullName || '')
setValue('phoneNumber', userInfo.phoneNumber || '')
setValue('dateOfBirth', userInfo.dateOfBirth || '')
setValue('nationalCode', userInfo.nationalCode || '')
setValue('address', userInfo.userAddress?.address || '')
}
}, [profile, setValue])
const onSubmit = async (data: Record<string, unknown>) => {
await updateProfileMutation.mutateAsync(data)
setIsEditing(false)
}
const handleSubmit = () => {
// Handle form submission
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault()
handleSubmit(onSubmit)(e)
}
const handleCancel = () => {
reset()
setIsEditing(false)
}
if (isLoading) {
return (
<div className="p-3 sm:p-4 md:p-6">
<Menu pageActive='profile' />
<div className='mt-4 sm:mt-6 md:mt-8 lg:mt-10'>
<div className="flex justify-center items-center h-64">
<div className="text-gray-500">در حال بارگذاری...</div>
</div>
</div>
</div>
)
}
return (
<div className="p-3 sm:p-4 md:p-6">
<Menu pageActive='profile' />
@@ -54,98 +108,74 @@ const ProfilePage = () => {
</Button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<Input
label="نام و نام خانوادگی"
value={userInfo.name}
onChange={(e) => handleInputChange('name', e.target.value)}
readOnly={!isEditing}
/>
<form onSubmit={handleFormSubmit}>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<div>
<Input
label="نام و نام خانوادگی"
{...register('fullName')}
readOnly={!isEditing}
error_text={errors.fullName?.message}
/>
</div>
<Input
label="کد ملی"
value={userInfo.nationalCode}
onChange={(e) => handleInputChange('nationalCode', e.target.value)}
readOnly={!isEditing}
/>
<div>
<Input
label="کد ملی"
{...register('nationalCode')}
readOnly={!isEditing}
error_text={errors.nationalCode?.message}
/>
</div>
<Input
label="ایمیل"
value={userInfo.email}
onChange={(e) => handleInputChange('email', e.target.value)}
readOnly={!isEditing}
type="email"
/>
<div>
<Input
label="شماره تماس"
{...register('phoneNumber')}
readOnly={!isEditing}
error_text={errors.phoneNumber?.message}
/>
</div>
<Input
label="شماره تماس"
value={userInfo.phone}
onChange={(e) => handleInputChange('phone', e.target.value)}
readOnly={!isEditing}
/>
<Input
label="تاریخ تولد"
value={userInfo.birthDate}
onChange={(e) => handleInputChange('birthDate', e.target.value)}
readOnly={!isEditing}
/>
<Input
label="استان"
value={userInfo.province}
onChange={(e) => handleInputChange('province', e.target.value)}
readOnly={!isEditing}
/>
<Input
label="شهر"
value={userInfo.city}
onChange={(e) => handleInputChange('city', e.target.value)}
readOnly={!isEditing}
/>
<Input
label="کد پستی"
value={userInfo.postalCode}
onChange={(e) => handleInputChange('postalCode', e.target.value)}
readOnly={!isEditing}
/>
<Input
label="پلاک"
value={userInfo.district}
onChange={(e) => handleInputChange('district', e.target.value)}
readOnly={!isEditing}
/>
</div>
<div className="mt-3 sm:mt-4">
<Input
label="آدرس"
value={userInfo.address}
onChange={(e) => handleInputChange('address', e.target.value)}
readOnly={!isEditing}
/>
</div>
{isEditing && (
<div className="mt-4 sm:mt-6 flex flex-col sm:flex-row gap-3">
<Button
onClick={handleSubmit}
className="px-6 sm:px-8 bg-blue-600 hover:bg-blue-700 text-sm sm:text-base"
>
ذخیره تغییرات
</Button>
<Button
onClick={() => setIsEditing(false)}
variant="outline"
className="text-sm sm:text-base"
>
انصراف
</Button>
<div>
<Input
label="تاریخ تولد"
{...register('dateOfBirth')}
readOnly={!isEditing}
error_text={errors.dateOfBirth?.message}
/>
</div>
</div>
)}
<div className="mt-3 sm:mt-4">
<Input
label="آدرس"
{...register('address')}
readOnly={!isEditing}
error_text={errors.address?.message}
/>
</div>
{isEditing && (
<div className="mt-4 sm:mt-6 flex flex-col sm:flex-row gap-3">
<Button
type="submit"
disabled={isSubmitting || updateProfileMutation.isPending}
className="px-6 sm:px-8 bg-blue-600 hover:bg-blue-700 text-sm sm:text-base"
>
{isSubmitting || updateProfileMutation.isPending ? 'در حال ذخیره...' : 'ذخیره تغییرات'}
</Button>
<Button
type="button"
onClick={handleCancel}
variant="outline"
className="text-sm sm:text-base"
>
انصراف
</Button>
</div>
)}
</form>
</div>
</div>
@@ -170,11 +200,9 @@ const ProfilePage = () => {
</div>
<div className='flex-1 bg-red-50 '>
{/* محتوای اضافی */}
</div>
</div>
</div>
</div>
</div>
+12
View File
@@ -1,7 +1,19 @@
import axios from "@/config/axios";
import { UserMeResponseType } from "@/share/types/SharedTypes";
import {
UpdateProfileRequest,
UpdateProfileResponse,
} from "../types/ProfileTypes";
export const getProfile = async () => {
const { data } = await axios.get<UserMeResponseType>("/user/profile");
return data;
};
export const updateProfile = async (profileData: UpdateProfileRequest) => {
const { data } = await axios.patch<UpdateProfileResponse>(
"/user/profile",
profileData
);
return data;
};
+60
View File
@@ -0,0 +1,60 @@
export interface ProfileFormData {
fullName: string;
phoneNumber: string;
dateOfBirth: string;
nationalCode: string;
address: string;
}
export interface UpdateProfileRequest {
fullName?: string;
phoneNumber?: string;
dateOfBirth?: string;
nationalCode?: string;
address?: string;
}
export interface UpdateProfileResponse {
status: number;
success: boolean;
results: {
info: {
_id: string;
fullName: string;
phoneNumber: string;
dateOfBirth: string | null;
address: string;
nationalCode: string | null;
homeNumber: string | null;
createdAt: string;
updatedAt: string;
userAddress: {
_id: string;
address: string;
city: {
_id: number;
name: string;
province: number;
createdAt: string;
updatedAt: string;
__v: number;
};
province: {
_id: number;
name: string;
createdAt: string;
updatedAt: string;
__v: number;
};
postalCode: string;
plaque: string;
lat: string;
lon: string;
createdAt: string;
updatedAt: string;
__v: number;
};
isSeller: boolean;
};
};
}