반응형
프로필 사진 변경 기능을 위해 클라이언트~클라우디너리 라이브러리까지 이미지를 보내는 로직을 구현해봤어요.
🚀 요약
작업 시간: 3시간
✅ 프로필 사진 변경 기능 구현
🚀 프로필 사진 변경 기능 구현
로직 흐름은 이렇습니다.
1. 사용자가 파일 선택
2. 임시 url을 만들어 preview 보여줌
3. 사용자가 저장 버튼 클릭 시 이미지를 Cloudinary에 업로드
4. 업로드 후 반환된 url을 DB에 업데이트
사용자가 파일을 선택할 때마다 바로바로 클라우드에 업로드 하면 최종 선택한 이미지를 제외하고 모두 올펀 에셋 (orphan essets)이 됩니다. 때문에 저장 버튼 클릭 이후에 클라우드로 전송하는 게 데이터 관리 측면에서 효율적일 수 있겠죠.
프로필 이미지 프리뷰를 보여주는 컴포넌트
import { useRef } from "react";
interface ProfileUploaderProps {
previewUrl: string | null;
onFileSelect: (file: File) => void;
}
const ProfileUploader = ({
previewUrl,
onFileSelect,
}: ProfileUploaderProps) => {
const inputRef = useRef<HTMLInputElement | null>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) onFileSelect(file);
};
return (
<div className="w-full flex flex-col items-center justify-center gap-2">
<div
className="relative w-[10rem] h-[10rem] m-1 rounded-full overflow-hidden cursor-pointer group"
onClick={() => inputRef.current?.click()}
>
<img
className="w-full h-full object-cover"
src={previewUrl || "/default.png"}
alt="profile"
/>
<div className="absolute inset-0 hidden group-hover:flex items-center justify-center bg-gray-800 bg-opacity-60">
<img
className="w-10 h-10 filter invert"
src="/images/icons/edit.png"
alt="edit"
/>
</div>
</div>
<input
ref={inputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleFileChange}
/>
</div>
);
};
export default ProfileUploader;
상위 프로필 컴포넌트에서 호출
저장 버튼 클릭 시, 클라우드 업로드와 DB 업데이트를 실행합니다.
<ProfileUploader
previewUrl={
selectedFile ? URL.createObjectURL(selectedFile) : profileImg
}
onFileSelect={setSelectedFile}
/>
<button
className={
(isPwdValid && isPwdConfirmed && isUsernameValid
? "bg-blue-600 border-blue-600 "
: "bg-gray-300 border-gray-300 ") +
"text-white m-1 py-2 px-4 border-2 rounded-md w-[20rem]"
}
disabled={!(isPwdValid && isPwdConfirmed && isUsernameValid)}
onClick={handleSaveClick}
>
Save Changes
</button>
const handleSaveClick = async () => {
let uploadedUrl = profileImg;
if (selectedFile) {
const formData = new FormData();
formData.append("file", selectedFile);
formData.append("upload_preset", UPLOAD_PRESET);
try {
const res = await fetch(
`https://api.cloudinary.com/v1_1/${CLOUD_NAME}/image/upload`,
{
method: "POST",
body: formData,
}
);
const data = await res.json();
uploadedUrl = data.secure_url;
} catch (err) {
setMsg("이미지 업로드에 실패했습니다.");
return;
}
}
axios
.put(
"http://localhost:5232/api/users/update",
{
Email: email,
PasswordHash: password,
Username: username,
ProfileImageUrl: uploadedUrl,
},
{ withCredentials: true }
)
.then((response) => {
if (response.data.message) {
setMsg(response.data.message);
setProfileImg(uploadedUrl); // 업로드 성공 시 이미지 적용
} else {
setMsg("Failed to update the profile.");
}
})
.catch((err) => {
setMsg("프로필 저장에 실패했습니다.");
});
};
오늘 작업 끝!
다음으로는 프로필 페이지 디테일을 좀 챙긴 후에, 대망의 게시글 업로드.. 페이지에 손을 대보려고 합니다.
반응형
'study > 100 days (100일 챌린지)' 카테고리의 다른 글
[웹개발 100일] Day 47~49 - 게시글 업로드 페이지 UI 구현 (0) | 2025.04.03 |
---|---|
[웹개발 100일] Day 44 - 클라우드 이미지 업로드 서비스 Cloudinary 사용하기 (0) | 2025.03.29 |
여러분 그간 (0) | 2025.03.27 |
[웹개발 100일] Day 34~35 - 프로필 (마이페이지) 수정사항 저장 기능 (2) | 2025.03.20 |
[웹개발 100일] Day 33 - 1주차 주간 정리 (0) | 2025.03.18 |