Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 59 additions & 56 deletions src/components/ProfileEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function ProfileEditModal({ open, onClose }: ProfileEditModalProp
const [file, setFile] = useState<File | null>(null);
const [loading, setLoading] = useState(false);

// 모달 열릴 때 닉네임 초기화
// 모달 열릴 때 기본 닉네임 세팅
useEffect(() => {
if (open && user) {
setNickname(user.nickname ?? "");
Expand All @@ -25,7 +25,38 @@ export default function ProfileEditModal({ open, onClose }: ProfileEditModalProp

if (!open || !user) return null;

// 닉네임 변경
// Presigned URL 발급
const requestPresignedUrl = async (file: File) => {
const res = await apiFetch(`${API_URL}/api/v1/file/presigned-url`, {
method: "POST",
body: JSON.stringify({
type: "PROFILE",
refId: Number(user.id),
contentType: file.type,
contentLength: file.size,
}),
});

const json = await res.json();
if (!json.isSuccess) throw new Error("Presigned URL 발급 실패");

return {
presignedUrl: json.result.presignedUrl,
publicUrl: json.result.publicUrl,
};
};

// S3 업로드
const uploadToS3 = async (presignedUrl: string, file: File) => {
const res = await fetch(presignedUrl, {
method: "PUT",
body: file,
headers: { "Content-Type": file.type },
});
if (!res.ok) throw new Error("S3 업로드 실패");
};

// 닉네임 수정
const handleNicknameUpdate = async () => {
if (!nickname.trim()) {
alert("닉네임을 입력해주세요.");
Expand All @@ -36,8 +67,8 @@ export default function ProfileEditModal({ open, onClose }: ProfileEditModalProp
`${API_URL}/api/v1/member/nickname?nickname=${encodeURIComponent(nickname)}`,
{ method: "PATCH" }
);
const json = await res.json();

const json = await res.json();
if (!json.isSuccess) {
alert(json.message ?? "닉네임 변경에 실패했습니다.");
return;
Expand All @@ -47,67 +78,37 @@ export default function ProfileEditModal({ open, onClose }: ProfileEditModalProp
alert("닉네임이 변경되었습니다.");
};

// 프로필 이미지 업로드
// =============================
// 프로필 이미지 업로드 전체 처리
// =============================
const handleUploadImage = async () => {
if (!file) return alert("변경할 이미지를 선택해주세요.");

setLoading(true);

try {
/** 1) Presigned URL 발급 */
const presignedRes = await apiFetch(
`${API_URL}/api/v1/file/presigned-url`,
{
method: "POST",
body: JSON.stringify({
type: "PROFILE",
refId: user.id, // ★ 유저 ID 필수
contentType: file.type,
contentLength: file.size,
}),
}
);

const presignedJson = await presignedRes.json();
if (!presignedJson.isSuccess) {
alert("Presigned URL 발급 실패");
return;
}
// 1) Presigned URL 요청
const { presignedUrl, publicUrl } = await requestPresignedUrl(file);

const { presignedUrl, publicUrl } = presignedJson.result;
// 2) S3 업로드
await uploadToS3(presignedUrl, file);

/** 2) S3 PUT 업로드 */
const uploadRes = await fetch(presignedUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file,
// 3) 업로드 확정 → DB 저장
const confirm = await apiFetch(`${API_URL}/api/v1/file/confirm`, {
method: "POST",
body: JSON.stringify({
type: "PROFILE",
refId: Number(user.id),
fileUrl: publicUrl,
}),
});

if (!uploadRes.ok) {
alert("S3 업로드 실패");
return;
}

/** 3) 업로드 확정 - DB 반영 */
const confirmRes = await apiFetch(
`${API_URL}/api/v1/file/confirm`,
{
method: "POST",
body: JSON.stringify({
type: "PROFILE",
refId: user.id,
fileUrl: publicUrl,
}),
}
);

const confirmJson = await confirmRes.json();
const confirmJson = await confirm.json();
if (!confirmJson.isSuccess) {
alert("이미지 반영 실패");
alert("프로필 반영 실패");
return;
}

// 4) 전역 유저 상태 업데이트
// 4) 전역 user 업데이트
setUser(prev => ({
...(prev || {}),
profileImageUrl: publicUrl,
Expand All @@ -117,21 +118,23 @@ export default function ProfileEditModal({ open, onClose }: ProfileEditModalProp
setFile(null);

} catch (err) {
console.error("프로필 이미지 업로드 중 오류:", err);
console.error("프로필 업로드 오류:", err);
alert("업로드 오류 발생");
} finally {
setLoading(false);
}
};

// 프로필 이미지 삭제
// 프로필 이미지 삭제
// =============================
// 프로필 이미지 삭제
// =============================
const handleDeleteImage = async () => {
try {
const res = await apiFetch(`${API_URL}/api/v1/file`, {
method: "DELETE",
body: JSON.stringify({
type: "PROFILE",
refId: user.id,
refId: Number(user.id),
}),
});

Expand All @@ -148,7 +151,7 @@ export default function ProfileEditModal({ open, onClose }: ProfileEditModalProp

alert("프로필 이미지가 삭제되었습니다.");
} catch (err) {
console.error("삭제 중 오류:", err);
console.error("프로필 삭제 오류:", err);
}
};

Expand Down
26 changes: 21 additions & 5 deletions src/pages/EventMainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ const handleSubmitGroupEdit = async () => {
let finalImageUrl = groupEditForm.imgUrl;

try {
// 1) 이미지 파일이 새로 선택된 경우 → presigned 발급 + S3 업로드
// 1) presigned + S3 업로드
if (groupImageFile) {
const { presignedUrl, publicUrl } = await requestPresignedUrl(
groupImageFile,
Expand All @@ -328,15 +328,31 @@ const handleSubmitGroupEdit = async () => {

await uploadToS3(presignedUrl, groupImageFile);

finalImageUrl = publicUrl;
// 2) 업로드 완료 → 백엔드에 확정 요청 (DB 업데이트)
const confirmRes = await apiFetch(`${API_URL}/api/v1/file/confirm`, {
method: "POST",
body: JSON.stringify({
type: "GROUP",
refId: Number(groupEditForm.groupId),
fileUrl: publicUrl,
}),
});

const confirmJson = await confirmRes.json();
if (!confirmJson.isSuccess) {
alert("이미지 업로드 확정 실패");
return;
}

finalImageUrl = confirmJson.result; // 백엔드에서 반환한 최종 URL
}

// 2) 그룹 정보 업데이트 API 호출
// 3) 그룹 정보 업데이트
const payload = {
groupId: Number(groupEditForm.groupId),
groupName: groupEditForm.groupName.trim(),
groupDescription: groupEditForm.groupDescription ?? "",
imgUrl: finalImageUrl
imgUrl: finalImageUrl,
};

const res = await apiFetch(`${API_URL}/api/v1/group`, {
Expand All @@ -350,7 +366,7 @@ const handleSubmitGroupEdit = async () => {
return;
}

// 3) UI 업데이트
// UI 갱신
setTeams((prev) =>
prev.map((team) =>
team.id === groupEditForm.groupId
Expand Down