avatar profile
This commit is contained in:
@@ -12,7 +12,7 @@ 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 { useGetProfile, useSingleUpload, useUpdateProfile } from '../hooks/userProfileData';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { UpdateProfileType } from '../types/Types';
|
import { UpdateProfileType } from '../types/Types';
|
||||||
@@ -23,9 +23,12 @@ type Props = object
|
|||||||
|
|
||||||
function ProfileIndex({ }: Props) {
|
function ProfileIndex({ }: Props) {
|
||||||
|
|
||||||
|
const { mutateAsync: singleUpload, isPending: isUploading } = useSingleUpload();
|
||||||
const { data, isPending } = useGetProfile();
|
const { data, isPending } = useGetProfile();
|
||||||
const { mutate: updateProfile, isPending: isUpdating } = useUpdateProfile();
|
const { mutate: updateProfile, isPending: isUpdating } = useUpdateProfile();
|
||||||
const [showCalendar, setShowCalendar] = React.useState(false);
|
const [showCalendar, setShowCalendar] = React.useState(false);
|
||||||
|
const [uploadedImageUrl, setUploadedImageUrl] = React.useState<string | null>(null);
|
||||||
|
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
const dateLib = getDateLib()
|
const dateLib = getDateLib()
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -117,6 +120,7 @@ function ProfileIndex({ }: Props) {
|
|||||||
lastName: submitValues.lastName,
|
lastName: submitValues.lastName,
|
||||||
birthDate: birthDate ? formatDateToString(birthDate) : undefined,
|
birthDate: birthDate ? formatDateToString(birthDate) : undefined,
|
||||||
gender: submitValues.gender,
|
gender: submitValues.gender,
|
||||||
|
avatarUrl: uploadedImageUrl || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateProfile(payload, {
|
updateProfile(payload, {
|
||||||
@@ -141,6 +145,10 @@ function ProfileIndex({ }: Props) {
|
|||||||
gender: userData.gender !== undefined ? userData.gender : undefined,
|
gender: userData.gender !== undefined ? userData.gender : undefined,
|
||||||
};
|
};
|
||||||
formik.setValues(values);
|
formik.setValues(values);
|
||||||
|
// اگر کاربر تصویر آواتار دارد، آن را نمایش بده
|
||||||
|
if (userData.avatarUrl) {
|
||||||
|
setUploadedImageUrl(userData.avatarUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [userData]);
|
}, [userData]);
|
||||||
@@ -155,6 +163,25 @@ function ProfileIndex({ }: Props) {
|
|||||||
formik.setFieldValue('gender', index === 0);
|
formik.setFieldValue('gender', index === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await singleUpload(file);
|
||||||
|
if (response?.data?.url) {
|
||||||
|
setUploadedImageUrl(response.data.url);
|
||||||
|
toast('تصویر با موفقیت آپلود شد', 'success');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast(extractErrorMessage(error), 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageClick = () => {
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return (
|
||||||
<div className='h-full noscrollbar overflow-y-auto flex flex-col items-center justify-center'>
|
<div className='h-full noscrollbar overflow-y-auto flex flex-col items-center justify-center'>
|
||||||
@@ -173,20 +200,38 @@ function ProfileIndex({ }: Props) {
|
|||||||
<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">
|
<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">
|
<input
|
||||||
<Image
|
ref={fileInputRef}
|
||||||
src={'/assets/images/user-avatar.png'}
|
type="file"
|
||||||
className='rounded-full'
|
accept="image/*"
|
||||||
alt='user avatar'
|
onChange={handleImageUpload}
|
||||||
width={96}
|
className="hidden"
|
||||||
height={96}
|
/>
|
||||||
/>
|
<button type="button" onClick={handleImageClick} disabled={isUploading} className="relative">
|
||||||
|
{uploadedImageUrl && (
|
||||||
|
<Image
|
||||||
|
src={uploadedImageUrl}
|
||||||
|
className='rounded-full'
|
||||||
|
alt='user avatar'
|
||||||
|
width={96}
|
||||||
|
height={96}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className='w-24 h-24 absolute top-0 left-0 rounded-full'
|
className={`w-24 h-24 ${uploadedImageUrl ? 'absolute top-0 left-0' : ''} rounded-full cursor-pointer flex items-center justify-center`}
|
||||||
style={{
|
style={uploadedImageUrl ? {
|
||||||
background: 'linear-gradient(180deg, rgba(0, 0, 0, 0) 51.56%, rgba(0, 0, 0, 0.5) 100%)'
|
// background: 'linear-gradient(180deg, rgba(0, 0, 0, 0) 51.56%, rgba(0, 0, 0, 0.5) 100%)'
|
||||||
|
} : {
|
||||||
|
background: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
border: '2px dashed rgba(0, 0, 0, 0.3)'
|
||||||
}}>
|
}}>
|
||||||
<Camera size={20} className='stroke-white absolute bottom-3 left-1/2 -translate-x-1/2' />
|
{isUploading ? (
|
||||||
|
<div className='text-disabled-text text-xs'>
|
||||||
|
در حال آپلود...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Camera size={24} className={uploadedImageUrl ? 'stroke-white' : 'stroke-disabled-text'} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -258,7 +303,7 @@ 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 type="submit" disabled={isUpdating || !formik.isValid}>
|
<Button type="submit" disabled={isUpdating || isUploading || !formik.isValid}>
|
||||||
{isUpdating ? 'در حال ذخیره...' : 'ذخیره'}
|
{isUpdating ? 'در حال ذخیره...' : 'ذخیره'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -16,3 +16,9 @@ export const useUpdateProfile = () => {
|
|||||||
mutationFn: api.updateProfile,
|
mutationFn: api.updateProfile,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useSingleUpload = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.singleUpload,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ function ProfileIndex({ }: Props) {
|
|||||||
<div className="mt-8 flex-1 h-full bg-container rounded-container w-full box-shadow-normal py-6 px-4 flex flex-col justify-between">
|
<div className="mt-8 flex-1 h-full bg-container rounded-container w-full box-shadow-normal py-6 px-4 flex flex-col justify-between">
|
||||||
|
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="flex items-center justify-start gap-4 pb-6 border-b-[1.5px] border-border">
|
<div className="flex items-center justify-start gap-3 pb-6 border-b-[1.5px] border-border">
|
||||||
<Image
|
<Image
|
||||||
src={'/assets/images/user-avatar.png'}
|
src={userData?.avatarUrl ? userData.avatarUrl : '/assets/images/avatar.svg'}
|
||||||
className='rounded-full'
|
className='rounded-full'
|
||||||
alt='user avatar'
|
alt='user avatar'
|
||||||
width={80}
|
width={65}
|
||||||
height={80}
|
height={65}
|
||||||
/>
|
/>
|
||||||
<div className='block'>
|
<div className='block'>
|
||||||
<div className='font-medium text-base '>
|
<div className='font-medium text-base '>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { api } from "@/config/axios";
|
import { api } from "@/config/axios";
|
||||||
import { GetProfileResponse, UpdateProfileType } from "../types/Types";
|
import {
|
||||||
|
GetProfileResponse,
|
||||||
|
SingleUploadResponse,
|
||||||
|
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");
|
||||||
@@ -10,3 +14,20 @@ export const updateProfile = async (params: UpdateProfileType) => {
|
|||||||
const { data } = await api.patch("/public/user/update", params);
|
const { data } = await api.patch("/public/user/update", params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const singleUpload = async (
|
||||||
|
file: File
|
||||||
|
): Promise<SingleUploadResponse> => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
const { data } = await api.post<SingleUploadResponse>(
|
||||||
|
`/public/single-file`,
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { BaseResponse } from "@/app/[name]/(Main)/types/Types";
|
||||||
|
|
||||||
export interface UserProfile {
|
export interface UserProfile {
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -14,6 +16,7 @@ export interface UserProfile {
|
|||||||
gender: boolean;
|
gender: boolean;
|
||||||
wallet: number;
|
wallet: number;
|
||||||
points: number;
|
points: number;
|
||||||
|
avatarUrl?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetProfileResponse {
|
export interface GetProfileResponse {
|
||||||
@@ -28,4 +31,18 @@ export interface UpdateProfileType {
|
|||||||
birthDate?: string;
|
birthDate?: string;
|
||||||
marriageDate?: string;
|
marriageDate?: string;
|
||||||
gender?: boolean;
|
gender?: boolean;
|
||||||
|
avatarUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UploadedFile = {
|
||||||
|
key: string;
|
||||||
|
url: string;
|
||||||
|
bucket: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SingleUploadResponseData = {
|
||||||
|
file: UploadedFile;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SingleUploadResponse = BaseResponse<SingleUploadResponseData>;
|
||||||
|
|||||||
Reference in New Issue
Block a user