juni
프로필 아바타 및 이미지 수정 본문
프로필 이미지를 아바타와 이미지로 별도 관리 예시
const profileFormSchema = z.object({
name: z
.string()
.min(1, {
message: "Your name must be at least 1 characters.",
})
.max(20, {
message: "Your name must be at most 20 characters.",
}),
nickname: z
.string()
.min(1, {
message: "Your nickname must be at least 1 characters.",
})
.max(20, {
message: "Your nickname must be at most 20 characters.",
}),
description: z.string().optional(),
avatar: z.array(z.string()).optional(),
profileImage: z.string().optional(),
});
const form = useForm<ProfileFormValues>({
resolver: zodResolver(profileFormSchema),
defaultValues: {
name: defaultValues?.name || "",
nickname: defaultValues?.nickname || "",
description: defaultValues?.description || "",
avatar: defaultValues?.avatar || [],
profileImage: defaultValues?.profileImage || "",
},
mode: "onChange",
});
<div className="flex justify-center gap-6">
<div>
<div
className="w-[120px] h-[120px] relative rounded-full border bg-border-div cursor-pointer mb-2"
onClick={() => router.push("/mypage/avatar")}
>
<IPFSImageLayer
hashes={user?.avatar}
className="w-full h-full object-contain rounded-full"
/>
</div>
<div
className="text-center text-body_s text-accent-tertiary cursor-pointer"
onClick={() => router.push("/mypage/avatar")}
>
아바타 편집
</div>
</div>
<div>
<div className="w-[120px] h-[120px] relative rounded-full border bg-border-div mb-2">
<ImageUploader
ref={imageUploaderRef}
defaultImage={user?.image}
onFileSelected={setProfileImage}
shape="circle"
/>
</div>
<div
className="text-center text-body_s text-accent-tertiary cursor-pointer"
onClick={handleButtonClick} // 클릭 시 handleButtonClick 호출
>
사진 업로드
</div>
</div>
</div>
이미지 변경 API 예시
// 프로필 이미지 변경
export const updateUserProfile = async ({
user_profile,
}: {
user_profile: File[];
}) => {
const formData = new FormData();
if (user_profile && user_profile.length > 0) {
formData.append("user_profile", user_profile[0]); // 파일 배열에서 첫 번째 파일을 추가
}
return await patchApi(`/users/profile`, formData, true);
};
부위별로 분리된 아바타 하나로 합쳐주는 컴포넌트 예시
const IPFSImageLayer: React.FC<IPFSImageLayerProps> = ({
hashes = [
"uploads/user/avatar/background.webp",
"uploads/user/avatar/clothes.webp",
"uploads/user/avatar/face.webp",
"uploads/user/avatar/eye.webp",
"uploads/user/avatar/hair.webp",
"uploads/user/avatar/mouth.webp",
"uploads/user/avatar/accessory.webp",
],
className,
}) => {
if (!hashes) {
return;
}
return hashes.map((url, index) => {
if (!hashes[index]) {
return;
}
return (
<Image
key={index}
src={`${DEFAULT_IMAGE_URL}${hashes[index]}`} // DB에서 받아오는 아바타 이미지
alt={`Layer ${url}`}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
}}
className={className}
width={300}
height={300}
priority={true}
/>
);
});
};
이미지 업로드 예시
useImperativeHandle(ref, () => fileInputRef.current!); // 외부 ref가 내부 fileInputRef를 참조하도록 설정
const handleFileChange = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
if (disabled) return;
const file = event.target.files?.[0];
if (file) {
const fileType = file.type.split("/")[0];
if (fileType === "image") {
try {
const webpBlob = await convertToWebP(file);
if (webpBlob) {
const webpUrl = URL.createObjectURL(webpBlob);
setMediaSource(webpUrl);
setMediaType(fileType);
onFileSelected?.(convertBlobToFile(webpBlob, file.name), webpUrl);
}
} catch (error) {
console.error("Error converting file to WebP:", error);
}
} else {
window.alert("Please upload an image format.");
}
}
};
'인턴' 카테고리의 다른 글
커뮤니티 (게시글 고정) API 연동 (2) | 2024.11.13 |
---|---|
게시글 고정 (1) | 2024.11.13 |
게시글 삭제, 상세 게시글 DropDownMenu 연결 (0) | 2024.11.12 |
게시글 생성/수정 ( defaultValues로 관리 ) (1) | 2024.11.12 |
모달, 토글 ( react-dialog, react-switch ), 조건부 랜더링 ( 등급별 ) (0) | 2024.11.11 |