-
Notifications
You must be signed in to change notification settings - Fork 3
[refactor] 관리자 페이지 활동사진 업로드 UI/UX 개선 #1360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop-fe
Are you sure you want to change the base?
Changes from 2 commits
555b29f
49f4060
4d7d029
8292748
003486d
9141885
5008638
fe242c2
d820915
adb292b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ import { handleResponse } from './utils/apiHelpers'; | |
| interface PresignedData { | ||
| presignedUrl: string; | ||
| finalUrl: string; | ||
| success: boolean; | ||
| failureReason: string | null; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 실패이유도 이제 추가되나보군요 |
||
| } | ||
|
|
||
| interface FeedUploadRequest { | ||
|
|
@@ -16,11 +18,13 @@ interface FeedUploadRequest { | |
| export async function uploadToStorage( | ||
| presignedUrl: string, | ||
| file: File, | ||
| contentType?: string, | ||
| ): Promise<void> { | ||
| const resolvedContentType = contentType || file.type || 'image/jpeg'; | ||
| const response = await fetch(presignedUrl, { | ||
| method: 'PUT', | ||
| body: file, | ||
| headers: { 'Content-Type': file.type }, | ||
| headers: { 'Content-Type': resolvedContentType }, | ||
| }); | ||
| await handleResponse(response, `S3 업로드 실패 : ${response.status}`); | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저장 로직이 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,13 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; | |
| import { feedApi, logoApi, uploadToStorage } from '@/apis/image'; | ||
| import { queryKeys } from '@/constants/queryKeys'; | ||
|
|
||
| type ItemStatus = 'pending' | 'uploading' | 'failed'; | ||
|
|
||
| interface FeedUploadParams { | ||
| clubId: string; | ||
| files: File[]; | ||
| existingUrls: string[]; | ||
| onItemStatusChange?: (index: number, status: ItemStatus) => void; | ||
| } | ||
|
|
||
| interface FeedUpdateParams { | ||
|
|
@@ -22,11 +25,12 @@ export const useUploadFeed = () => { | |
| const queryClient = useQueryClient(); | ||
|
|
||
| return useMutation({ | ||
| mutationFn: async ({ clubId, files, existingUrls }: FeedUploadParams) => { | ||
| mutationFn: async ({ clubId, files, existingUrls, onItemStatusChange }: FeedUploadParams) => { | ||
| // 1. presigned URL 요청 | ||
| const ALLOWED_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/bmp', 'image/webp']; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 밖으로 빼는 게 나아보입니다!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. svg는 지원 안 하는거였죠?? |
||
| const uploadRequests = files.map((file) => ({ | ||
| fileName: file.name, | ||
| contentType: file.type, | ||
| contentType: ALLOWED_TYPES.includes(file.type) ? file.type : 'image/jpeg', | ||
| })); | ||
| const feedResArr = await feedApi.getUploadUrls(clubId, uploadRequests); | ||
|
|
||
|
|
@@ -35,10 +39,14 @@ export const useUploadFeed = () => { | |
| } | ||
|
|
||
| // 2. r2에 병렬 업로드 (개별 성공/실패 추적) | ||
| // presigned URL 생성 자체가 실패한 항목은 업로드 건너뜀 | ||
| const uploadResults = await Promise.allSettled( | ||
| files.map((file, i) => | ||
| uploadToStorage(feedResArr[i].presignedUrl, file), | ||
| ), | ||
| files.map((file, i) => { | ||
| if (!feedResArr[i].success || !feedResArr[i].presignedUrl) { | ||
| return Promise.reject(new Error(feedResArr[i].failureReason ?? 'presigned URL 생성 실패')); | ||
| } | ||
| return uploadToStorage(feedResArr[i].presignedUrl, file); | ||
| }), | ||
| ); | ||
|
|
||
| // 3. 성공한 파일만 추출 | ||
|
|
@@ -50,6 +58,7 @@ export const useUploadFeed = () => { | |
| successfulUrls.push(feedResArr[i].finalUrl); | ||
| } else { | ||
| failedFiles.push(files[i].name); | ||
| onItemStatusChange?.(i, 'failed'); | ||
| } | ||
| }); | ||
|
|
||
|
|
@@ -64,8 +73,8 @@ export const useUploadFeed = () => { | |
| // 6. 서버에 전체 배열 PUT으로 갱신 | ||
| await feedApi.updateFeeds(clubId, allUrls); | ||
|
|
||
| // 7. 실패한 파일 정보 반환 | ||
| return { clubId, failedFiles }; | ||
| // 7. 실패한 파일 정보 및 성공 URL 반환 | ||
| return { clubId, failedFiles, successfulUrls }; | ||
| }, | ||
|
|
||
| onSuccess: (data) => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
presign 요청에서 MIME fallback(
image/jpeg)을 쓰는 경우, 실제 PUT 업로드에도 동일한 content-type을 전달해야 서명 헤더가 일치합니다. 현재 upload 호출에서 보정된contentType전달이 빠져 있어 4xx(서명 불일치) 리스크가 있습니다.