prile form:
This commit is contained in:
@@ -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 * as api from "../service/Service";
|
||||||
import { useSharedStore } from "@/share/store/sharedStore";
|
import { useSharedStore } from "@/share/store/sharedStore";
|
||||||
|
import { UpdateProfileRequest } from "../types/ProfileTypes";
|
||||||
|
|
||||||
export const useGetProfile = () => {
|
export const useGetProfile = () => {
|
||||||
const { isLogin } = useSharedStore();
|
const { isLogin } = useSharedStore();
|
||||||
@@ -10,3 +11,18 @@ export const useGetProfile = () => {
|
|||||||
enabled: isLogin,
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
+101
-73
@@ -1,40 +1,94 @@
|
|||||||
'use client'
|
'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 { Button } from '@/components/ui/button'
|
||||||
import Input from '@/components/Input'
|
import Input from '@/components/Input'
|
||||||
import { Edit2 } from 'lucide-react'
|
import { Edit2 } from 'lucide-react'
|
||||||
import Layout from '@/hoc/Layout'
|
import Layout from '@/hoc/Layout'
|
||||||
import { Heart, ShoppingCart } from 'iconsax-react'
|
import { Heart, ShoppingCart } from 'iconsax-react'
|
||||||
import Menu from './components/Menu'
|
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 ProfilePage = () => {
|
||||||
const [userInfo, setUserInfo] = useState({
|
const { data: profile, isLoading } = useGetProfile()
|
||||||
name: 'حسین دهقانی',
|
const updateProfileMutation = useUpdateProfile()
|
||||||
email: 'sondos@gmail.com',
|
|
||||||
phone: '09010901789',
|
|
||||||
nationalCode: '052093452',
|
|
||||||
birthDate: '1365/5/8',
|
|
||||||
postalCode: '1235478951',
|
|
||||||
province: 'مرکزی',
|
|
||||||
city: 'اراک',
|
|
||||||
address: 'خیابان دانشگاه رودوی بیمارستان خوانساری ساختمان مهر طبقه سوم واحد2',
|
|
||||||
district: '58'
|
|
||||||
})
|
|
||||||
|
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
|
|
||||||
const handleInputChange = (field: string, value: string) => {
|
const {
|
||||||
setUserInfo(prev => ({
|
register,
|
||||||
...prev,
|
handleSubmit,
|
||||||
[field]: value
|
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 = () => {
|
const handleFormSubmit = (e: React.FormEvent) => {
|
||||||
// Handle form submission
|
e.preventDefault()
|
||||||
|
handleSubmit(onSubmit)(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
reset()
|
||||||
setIsEditing(false)
|
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 (
|
return (
|
||||||
<div className="p-3 sm:p-4 md:p-6">
|
<div className="p-3 sm:p-4 md:p-6">
|
||||||
<Menu pageActive='profile' />
|
<Menu pageActive='profile' />
|
||||||
@@ -54,91 +108,66 @@ const ProfilePage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleFormSubmit}>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||||
|
<div>
|
||||||
<Input
|
<Input
|
||||||
label="نام و نام خانوادگی"
|
label="نام و نام خانوادگی"
|
||||||
value={userInfo.name}
|
{...register('fullName')}
|
||||||
onChange={(e) => handleInputChange('name', e.target.value)}
|
|
||||||
readOnly={!isEditing}
|
readOnly={!isEditing}
|
||||||
|
error_text={errors.fullName?.message}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
<Input
|
<Input
|
||||||
label="کد ملی"
|
label="کد ملی"
|
||||||
value={userInfo.nationalCode}
|
{...register('nationalCode')}
|
||||||
onChange={(e) => handleInputChange('nationalCode', e.target.value)}
|
|
||||||
readOnly={!isEditing}
|
readOnly={!isEditing}
|
||||||
|
error_text={errors.nationalCode?.message}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Input
|
<div>
|
||||||
label="ایمیل"
|
|
||||||
value={userInfo.email}
|
|
||||||
onChange={(e) => handleInputChange('email', e.target.value)}
|
|
||||||
readOnly={!isEditing}
|
|
||||||
type="email"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label="شماره تماس"
|
label="شماره تماس"
|
||||||
value={userInfo.phone}
|
{...register('phoneNumber')}
|
||||||
onChange={(e) => handleInputChange('phone', e.target.value)}
|
|
||||||
readOnly={!isEditing}
|
readOnly={!isEditing}
|
||||||
|
error_text={errors.phoneNumber?.message}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
<Input
|
<Input
|
||||||
label="تاریخ تولد"
|
label="تاریخ تولد"
|
||||||
value={userInfo.birthDate}
|
{...register('dateOfBirth')}
|
||||||
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}
|
readOnly={!isEditing}
|
||||||
|
error_text={errors.dateOfBirth?.message}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 sm:mt-4">
|
<div className="mt-3 sm:mt-4">
|
||||||
<Input
|
<Input
|
||||||
label="آدرس"
|
label="آدرس"
|
||||||
value={userInfo.address}
|
{...register('address')}
|
||||||
onChange={(e) => handleInputChange('address', e.target.value)}
|
|
||||||
readOnly={!isEditing}
|
readOnly={!isEditing}
|
||||||
|
error_text={errors.address?.message}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<div className="mt-4 sm:mt-6 flex flex-col sm:flex-row gap-3">
|
<div className="mt-4 sm:mt-6 flex flex-col sm:flex-row gap-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
type="submit"
|
||||||
|
disabled={isSubmitting || updateProfileMutation.isPending}
|
||||||
className="px-6 sm:px-8 bg-blue-600 hover:bg-blue-700 text-sm sm:text-base"
|
className="px-6 sm:px-8 bg-blue-600 hover:bg-blue-700 text-sm sm:text-base"
|
||||||
>
|
>
|
||||||
ذخیره تغییرات
|
{isSubmitting || updateProfileMutation.isPending ? 'در حال ذخیره...' : 'ذخیره تغییرات'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setIsEditing(false)}
|
type="button"
|
||||||
|
onClick={handleCancel}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="text-sm sm:text-base"
|
className="text-sm sm:text-base"
|
||||||
>
|
>
|
||||||
@@ -146,6 +175,7 @@ const ProfilePage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -170,11 +200,9 @@ const ProfilePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex-1 bg-red-50 '>
|
<div className='flex-1 bg-red-50 '>
|
||||||
|
{/* محتوای اضافی */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
import axios from "@/config/axios";
|
import axios from "@/config/axios";
|
||||||
import { UserMeResponseType } from "@/share/types/SharedTypes";
|
import { UserMeResponseType } from "@/share/types/SharedTypes";
|
||||||
|
import {
|
||||||
|
UpdateProfileRequest,
|
||||||
|
UpdateProfileResponse,
|
||||||
|
} from "../types/ProfileTypes";
|
||||||
|
|
||||||
export const getProfile = async () => {
|
export const getProfile = async () => {
|
||||||
const { data } = await axios.get<UserMeResponseType>("/user/profile");
|
const { data } = await axios.get<UserMeResponseType>("/user/profile");
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateProfile = async (profileData: UpdateProfileRequest) => {
|
||||||
|
const { data } = await axios.patch<UpdateProfileResponse>(
|
||||||
|
"/user/profile",
|
||||||
|
profileData
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user