Skip to content

[feature] 상세페이지 활동사진 추가#238

Merged
seongwon030 merged 44 commits intodevelop/fefrom
feature/#235-add-photo-component-FE-45
Apr 2, 2025
Merged

[feature] 상세페이지 활동사진 추가#238
seongwon030 merged 44 commits intodevelop/fefrom
feature/#235-add-photo-component-FE-45

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Mar 26, 2025

#️⃣연관된 이슈

ex) #235

📝작업 내용

활동사진 추가 2087981

스크린샷 2025-03-27 01 55 40

구글드라이브 url 변환 유틸함수 0e7e9c1

  • Google Drive URL에서 파일 ID를 추출
  • 썸네일 이미지로 바로 접근할 수 있는 URL로 변환
  • 지연로딩을 위해 LazyImage 유틸함수 구현 3762d26

활동사진 컴포넌트 내 hook 분리

  • 버튼 기반 네비게이션 함수 ca4c46d

스크롤 인덱스 enum 추가 bb842b9

  • 기존
sectionRefs.current[2] = el;
  • 변경 후
sectionRefs.current[SECTION_INDEX.DESCRIPTION_TAB] = el;

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

  • 스와이프 기능 추가
  • Hook 리팩토링
  • 모바일일 때 마지막 카드 안 보임 해결

@seongwon030 seongwon030 added ✨ Feature 기능 개발 💻 FE Frontend labels Mar 26, 2025
@seongwon030 seongwon030 requested a review from oesnuj March 26, 2025 17:01
@seongwon030 seongwon030 self-assigned this Mar 26, 2025
@netlify
Copy link

netlify bot commented Mar 26, 2025

Deploy Preview for moadong ready!

Name Link
🔨 Latest commit 59b633b
🔍 Latest deploy log https://app.netlify.com/sites/moadong/deploys/67ecde9099bf610008f6751d
😎 Deploy Preview https://deploy-preview-238--moadong.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@seongwon030 seongwon030 linked an issue Mar 26, 2025 that may be closed by this pull request
4 tasks
@Moadong Moadong deleted a comment from coderabbitai bot Mar 26, 2025
@Moadong Moadong deleted a comment from coderabbitai bot Mar 26, 2025
@seongwon030
Copy link
Member Author

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.
Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
🪧 Tips

@coderabbitai review

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (12)
frontend/src/utils/convertGoogleDriveUrl.ts (1)

10-10: 로그 메시지 언어 일관성 검토

에러 로그 메시지가 한국어로 작성되어 있습니다. 코드베이스의 로그 메시지 언어 정책에 따라 일관성 있게 사용하는 것이 좋습니다.

- console.error('URL 변환 중 오류 발생:', error);
+ console.error('Error during URL conversion:', error);
frontend/src/hooks/LazyImage.tsx (1)

1-40: 잘 구현된 LazyImage 컴포넌트입니다!

이미지 지연 로딩을 위한 컴포넌트가 효율적으로 구현되었습니다. IntersectionObserver를 사용하여 뷰포트에 이미지가 들어올 때만 실제 이미지를 로드하는 방식은 성능 최적화에 매우 좋습니다.

몇 가지 개선 가능한 제안 사항:

  1. 이미지 로딩 실패 시 대체 UI(fallback)를 제공하는 것이 좋을 것 같습니다.
  2. 로딩 중 상태를 표시하는 스켈레톤 UI나 로딩 인디케이터를 추가하는 것을 고려해보세요.
 return isVisible ? (
   <img ref={imgRef} src={src} alt={alt} onError={onError} />
 ) : (
-  <div ref={imgRef} style={{ width: '100%', height: '100%' }} />
+  <div ref={imgRef} style={{ width: '100%', height: '100%', backgroundColor: '#f0f0f0' }} />
 );
frontend/src/hooks/usePhotoNavigation.ts (1)

1-67: 잘 구현된 사진 네비게이션 훅입니다.

사진 갤러리 네비게이션을 위한 커스텀 훅이 체계적으로 구현되었습니다. 다양한 상태 조건을 계산하고 적절한 핸들러 함수를 제공하여 복잡한 네비게이션 로직을 잘 추상화했습니다.

몇 가지 개선 가능한 제안 사항:

  1. 각 조건문과 계산에 주석을 추가하여 로직의 이해도를 높이는 것이 좋을 것 같습니다.
  2. containerWidth - 400에서 사용된 숫자 400은 매직 넘버로 보입니다. 이 값을 의미 있는 상수나 파라미터로 추출하는 것이 좋겠습니다.
  3. 복잡한 조건문(특히 canScrollRight)에 대한 설명을 추가하면 코드의 가독성이 향상될 것입니다.
+ // 마지막 카드가 표시 영역 중앙에 위치하는지 확인
  const isLastCardInMiddle =
    isLastCard && containerWidth > 0 && translateX > -(containerWidth - 400);
+ // 마지막에서 두 번째 카드가 시작 위치에 있는지 확인
  const isSecondLastCardAtStart =
    currentIndex === photosLength - 2 && translateX === 0;
+ // 오른쪽으로 스크롤 가능한지 확인 (다음 사진이 있고, 특수 케이스가 아닌 경우)
  const canScrollRight =
    currentIndex < photosLength - 1 &&
    !isLastCardInMiddle &&
    !isSecondLastCardAtStart &&
    currentIndex < photosLength - 2 &&
    photosLength > 2;
frontend/src/hooks/usePhotoSwipe.ts (4)

24-25: 주석을 영어로 번역하는 것이 좋습니다.

코드베이스의 일관성을 유지하기 위해 한글 주석을 영어로 번역하는 것이 좋습니다.

-  isLastCardInMiddle, // 	마지막 카드를 절반만 보여주는 상태인지
-  isSecondLastCardAtStart, // 마지막에서 2번째 카드가 가장 왼쪽에 있을 때인지
+  isLastCardInMiddle, // Whether the last card is shown only halfway
+  isSecondLastCardAtStart, // Whether the second last card is positioned at the leftmost

43-43: 매직 넘버를 상수로 추출하세요.

코드에 하드코딩된 숫자(100, 400, 50 등)를 상수로 추출하면 가독성과 유지보수성이 향상됩니다.

+ const MAX_SLIDE_DISTANCE_MOBILE = 100;
+ const MAX_SLIDE_DISTANCE_DESKTOP = cardWidth;
+ const MIN_WIDTH_THRESHOLD = 500;
+ const CARD_VIEW_WIDTH = 400;
+ const SWIPE_THRESHOLD_MOBILE = 50;
+ const SWIPE_THRESHOLD_DESKTOP = 100;

  const maxSlideDistance = window.innerWidth <= 500 ? 100 : cardWidth;
  
  // 이후 코드에서도 해당 상수 사용
  // if (newTranslate < containerWidth - 400) 등

Also applies to: 62-62, 75-75


51-67: 복잡한 조건부 로직에 주석을 추가하세요.

handleTouchMove 함수의 복잡한 조건문에 각 조건이 처리하는 상황에 대한 주석을 추가하면 코드의 의도를 더 명확하게 이해할 수 있습니다.

+    // 마지막 사진에서 오른쪽으로 더 이상 스와이프되지 않도록 방지
     if (currentIndex === photosLength - 1 && newTranslate > 0) {
       setTranslateX(0);
+    // 첫 번째 사진에서 왼쪽으로 더 이상 스와이프되지 않도록 방지
     } else if (currentIndex === 0 && newTranslate < 0) {
       setTranslateX(0);
+    // 마지막에서 두 번째 이상의 사진에서 왼쪽으로 스와이프 제한
     } else if (
       currentIndex >= photosLength - 2 &&
       newTranslate < currentTranslate
     ) {
       setTranslateX(currentTranslate);
+    // 마지막 사진의 위치 조정 (컨테이너 너비에 맞춤)
     } else if (
       currentIndex === photosLength - 1 &&
       newTranslate < containerWidth - 400
     ) {
       setTranslateX(containerWidth - 400);
     } else {
       setTranslateX(newTranslate);
     }

70-93: handleTouchEnd 함수에도 주석 추가가 필요합니다.

handleTouchEnd 함수의 조건 로직에도 주석을 추가하여 각 조건의 의도를 명확히 하는 것이 좋습니다.

   const handleTouchEnd = () => {
     if (!isDragging) return;
     setIsDragging(false);

     const diff = currentTranslate - translateX;
     const threshold = window.innerWidth <= 500 ? 50 : 100;

+    // 스와이프 거리가 임계값을 넘었을 때 페이지 전환
     if (Math.abs(diff) > threshold) {
+      // 오른쪽으로 스와이프하여 이전 사진으로 이동
       if (diff > 0 && currentIndex > 0) {
         handlePrev();
+      // 왼쪽으로 스와이프하여 다음 사진으로 이동 (특정 조건 충족시)
       } else if (
         diff < 0 &&
         currentIndex < photosLength - 1 &&
         !isLastCardInMiddle &&
         !isSecondLastCardAtStart
       ) {
         handleNext();
       } else {
         setTranslateX(currentTranslate);
       }
     } else {
+      // 스와이프 거리가 임계값을 넘지 않으면 원래 위치로 복원
       setTranslateX(currentTranslate);
     }
   };
frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.tsx (3)

23-23: 하드코딩된 값을 상수로 추출하세요.

cardWidth = 428 값을 상수 또는 테마 변수로 추출하여 코드의 유지보수성을 높이는 것이 좋습니다. 이는 다른 곳에서도 이 값을 참조할 수 있도록 해줍니다.

-  const cardWidth = 428; // 400px + 28px gap
+  const CARD_WIDTH = 428; // 400px + 28px gap
+  // 또는 상수 파일이나 테마에서 가져오는 것이 더 좋습니다
+  // import { CARD_WIDTH } from '@/constants/photoList';

80-101: handleProgressBarClick 함수에 주석을 추가하세요.

handleProgressBarClick 함수의 로직이 복잡하므로, 주석을 추가하여 각 단계의 의도를 명확히 하는 것이 좋습니다.

   const handleProgressBarClick = (e: React.MouseEvent<HTMLDivElement>) => {
     if (!progressBarRef.current) return;

+    // 프로그레스 바의 위치와 크기를 가져옴
     const rect = progressBarRef.current.getBoundingClientRect();
+    // 클릭한 위치의 X 좌표 계산
     const x = e.clientX - rect.left;
+    // 클릭 위치를 퍼센트로 변환
     const percentage = (x / rect.width) * 100;
+    // 퍼센트를 기반으로 새로운 인덱스 계산
     const newIndex = Math.floor((percentage / 100) * photos.length);

+    // 유효한 새 인덱스인지 확인하고 변경 적용
     if (
       newIndex !== currentIndex &&
       newIndex >= 0 &&
       newIndex < photos.length
     ) {
       const diff = newIndex - currentIndex;
       setCurrentIndex(newIndex);
+      // 마지막 사진으로 이동한 경우 특수 처리
       if (newIndex === photos.length - 1) {
         setTranslateX(containerWidth - 400);
       } else {
+        // 일반적인 경우 카드 너비에 따라 이동
         setTranslateX((prev) => prev - diff * cardWidth);
       }
     }
   };

109-162: 키보드 접근성을 추가하는 것이 좋습니다.

현재 구현은 터치 및 마우스 이벤트에만 초점을 맞추고 있습니다. 키보드 접근성을 향상시키기 위해 화살표 키를 사용한 사진 탐색 기능을 추가하는 것이 좋습니다.

+ // 컴포넌트 상단에 다음 함수 추가
+ const handleKeyDown = useCallback(
+   (e: KeyboardEvent) => {
+     if (e.key === 'ArrowLeft' && canScrollLeft) {
+       handlePrev();
+     } else if (e.key === 'ArrowRight' && canScrollRight) {
+       handleNext();
+     }
+   },
+   [canScrollLeft, canScrollRight, handleNext, handlePrev]
+ );
+
+ useEffect(() => {
+   window.addEventListener('keydown', handleKeyDown);
+   return () => {
+     window.removeEventListener('keydown', handleKeyDown);
+   };
+ }, [handleKeyDown]);

  // PhotoListContainer에 tabIndex 속성 추가
  <Styled.PhotoListContainer
+   tabIndex={0}
    ref={(el) => {
      sectionRefs.current[3] = el;
    }}>
frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.styles.ts (2)

95-95: 주석 처리된 z-index 속성을 처리하세요.

주석 처리된 z-index 속성이 있습니다. 필요하지 않다면 제거하고, 필요하다면 주석을 해제하고 주석에 이유를 설명하는 것이 좋습니다.

-  /* z-index: 2; */
+  z-index: 2; // 네비게이션 버튼이 다른 요소 위에 표시되도록 함

또는

-  /* z-index: 2; */

9-10: 하드코딩된 색상 값을 테마 변수로 추출하세요.

여러 곳에서 하드코딩된 색상 값(#dcdcdc, #cdcdcd, rgba(255, 255, 255, 0.9) 등)을 사용하고 있습니다. 이러한 값들을 테마 변수나 상수로 추출하면 일관된 디자인을 유지하고 유지보수성을 향상시킬 수 있습니다.

+ // theme.ts 또는 constants 파일에 추가
+ export const colors = {
+   border: '#dcdcdc',
+   background: {
+     light: '#f5f5f5',
+     card: '#cdcdcd',
+     button: 'rgba(255, 255, 255, 0.9)',
+     buttonHover: 'rgba(255, 255, 255, 1)',
+   },
+   text: {
+     placeholder: '#666',
+   },
+   progressBar: 'rgba(0, 0, 0, 0.2)',
+ };

  // 그리고 각 스타일 컴포넌트에서 이런 식으로 사용:
  border: 1px solid ${({ theme }) => theme.colors.border};
  background-color: ${({ theme }) => theme.colors.background.card};

Also applies to: 61-62, 86-87, 135-136, 149-150

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 292975a and 1880f06.

📒 Files selected for processing (15)
  • frontend/src/hooks/LazyImage.tsx (1 hunks)
  • frontend/src/hooks/queries/club/useUpdateClubDescription.ts (1 hunks)
  • frontend/src/hooks/queries/club/useUpdateClubDetail.ts (1 hunks)
  • frontend/src/hooks/usePhotoNavigation.ts (1 hunks)
  • frontend/src/hooks/usePhotoSwipe.ts (1 hunks)
  • frontend/src/pages/AdminPage/components/ImagePreview/ImagePreview.tsx (1 hunks)
  • frontend/src/pages/AdminPage/components/ImageUpload/ImageUpload.tsx (2 hunks)
  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts (1 hunks)
  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (3 hunks)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2 hunks)
  • frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.tsx (1 hunks)
  • frontend/src/types/club.ts (1 hunks)
  • frontend/src/utils/convertGoogleDriveUrl.ts (1 hunks)
🔇 Additional comments (18)
frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.styles.ts (1)

19-19: 모바일 뷰에서의 시각적 구분을 위한 적절한 스타일 추가

모바일 뷰에서 하단 테두리를 추가하여 소개 박스와 새로 추가된 활동사진 섹션 간의 시각적 구분을 만들어 주었습니다. 이는 사용자 경험을 향상시키는 좋은 개선사항입니다.

frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts (1)

105-105: 이미지 컨테이너 간격 조정으로 UI 개선

ImageContainer의 gap 값을 37px에서 15px로 줄여 더 컴팩트한 레이아웃을 구성했습니다. 이는 이미지 관련 UI 요소들 간의 간격을 적절히 조정하여 시각적으로 더 좋은 사용자 경험을 제공하는 변경사항입니다.

frontend/src/hooks/queries/club/useUpdateClubDetail.ts (1)

1-14:

Details

❓ Verification inconclusive

쿼리 캐시 무효화 로직 제거 검토 필요

useQueryClient와 관련된 onSuccess 콜백을 제거하여 코드가 단순화되었습니다. 이는 의도적인 아키텍처 변경으로 보이지만, 이 변경으로 인해 데이터 갱신 로직이 어디서 처리되는지 확인이 필요합니다.

이 변경이 의도적인 것이라면 문제가 없지만, 만약 실수로 제거된 것이라면 데이터가 갱신되지 않는 문제가 발생할 수 있습니다. 동일한 변경이 useUpdateClubDescription.ts에도 적용된 것으로 보아 의도적인 아키텍처 변경으로 보입니다.

다음 명령어를 통해 캐시 무효화 로직이 다른 곳에서 처리되는지 확인해보세요:


🏁 Script executed:

#!/bin/bash
# clubDetail 쿼리 무효화 로직이 어디에서 처리되는지 확인
echo "클럽 디테일 쿼리 무효화 로직 검색:"
rg -n "invalidateQueries\(\[?['\"]clubDetail" --type ts

Length of output: 110


쿼리 캐시 무효화 로직 관련 검토 요청

현재 useUpdateClubDetail 훅에서 쿼리 캐시 무효화 로직(예: useQueryClient를 통한 invalidateQueries 호출)이 제거되어 코드가 단순화되었습니다. 의도한 아키텍처 변경이라면 데이터 갱신 처리가 다른 곳에서 이루어지고 있어야 하는데,

  • 검색 결과에서는 'clubDetail' 관련 invalidateQueries 호출이 확인되지 않았습니다.
  • 동일한 변경이 적용된 useUpdateClubDescription.ts 등 다른 곳에서도 동일한 무효화 로직 제거가 이루어졌는지, 그리고 데이터 갱신 처리가 어디서 담당되고 있는지 재검토가 필요합니다.

변경 의도라면 관련 로직이 다른 부분에 구현되어 있는지, 혹은 누락으로 인한 데이터 갱신 문제는 없는지 수동으로 추가 확인 부탁드립니다.

frontend/src/types/club.ts (1)

27-35: 이미지 관련 컴포넌트의 재사용 가능한 타입 정의 추가

ImageUploadProps와 ImagePreviewProps 인터페이스 추가는 코드의 타입 안전성과 재사용성을 향상시킵니다. 이미지 관련 컴포넌트에서 사용되는 프롭스 타입을 중앙 집중화하여 관리함으로써 코드 일관성이 개선되었습니다.

이 변경사항은 PR 목표 중 하나인 "활동 사진 추가" 기능과 완벽하게 일치하며, 코드베이스의 유지 관리성을 향상시키는 좋은 리팩토링입니다.

frontend/src/hooks/queries/club/useUpdateClubDescription.ts (1)

1-14: 코드 간소화로 인한 관심사 분리가 잘 이루어졌습니다.

useQueryClient와 관련된 로직이 제거되어 더 간결해졌네요. 이전에는 mutation 성공 후 자동으로 clubDetail 쿼리를 무효화했을 텐데, 이제는 상위 컴포넌트에서 필요에 따라 처리하는 방식으로 변경된 것 같습니다. 이렇게 관심사를 분리하는 것은 코드의 유지보수성과 재사용성을 높이는 좋은 접근입니다.

frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2)

10-10: 새로운 기능 추가가 깔끔하게 이루어졌습니다.

PhotoList 컴포넌트의 추가로 상세페이지에 활동사진 기능이 구현되었네요. 이는 PR 목표와 일치하는 좋은 변경사항입니다.


60-60: 컴포넌트 통합이 잘 이루어졌습니다.

PhotoList 컴포넌트가 기존 페이지 구조에 자연스럽게 통합되었습니다. sectionRefsfeeds props를 적절하게 전달하여 스크롤 기능과 데이터를 모두 처리할 수 있게 했네요.

frontend/src/utils/convertGoogleDriveUrl.ts (1)

1-13: Google Drive URL 변환 유틸리티가 효율적으로 구현되었습니다.

정규식을 사용해 Google Drive 파일 ID를 추출하고 썸네일 URL로 변환하는 로직이 명확하게 구현되었습니다. 두 가지 형식의 URL(id= 쿼리 파라미터와 /d/ 경로 세그먼트)을 모두 처리할 수 있는 것은 좋은 접근입니다.

오류 처리도 적절히 구현되어 있어 예외 상황에서도 원본 URL이 반환됩니다.

frontend/src/pages/AdminPage/components/ImagePreview/ImagePreview.tsx (1)

3-5: 타입 관리가 개선되었습니다.

인라인 타입 정의 대신 중앙화된 타입 정의를 가져와 사용하도록 리팩토링했네요. 이는 코드의 일관성과 유지보수성을 향상시키는 좋은 변화입니다. 동일한 타입이 여러 컴포넌트에서 재사용될 때 특히 유용합니다.

frontend/src/pages/AdminPage/components/ImageUpload/ImageUpload.tsx (2)

5-10: 타입 리팩토링이 잘 되었습니다.

외부 타입 정의(ImageUploadProps)를 사용하여 컴포넌트 props를 개선한 것은 좋은 변경입니다. 이렇게 하면 타입 정의를 중앙화하고 재사용성을 높일 수 있습니다.


40-40: default export 추가 확인

default export를 추가한 것은 컴포넌트 임포트 방식의 일관성을 위한 좋은 변경입니다.

frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (5)

1-16: 임포트 구문 및 타입 구성이 개선되었습니다.

임포트 구문의 재배치와 타입 가져오기가 잘 정리되었습니다. 컴포넌트 임포트도 일관된 방식으로 변경된 점이 좋습니다.


17-18: 상수 선언 위치 개선

MAX_IMAGESTEMP_CLUB_ID 상수를 파일 상단으로 이동한 것은 좋은 변경입니다. 다만, TEMP_CLUB_ID는 임시적인 값으로 보이는데, 향후 실제 값으로 대체하는 것을 고려해야 합니다.

TEMP_CLUB_ID가 실제 개발/프로덕션 환경에서 어떻게 사용될 예정인지 확인해주세요. 임시 값이 프로덕션 코드에 남아있지 않도록 주의가 필요합니다.


31-31: 상태 초기화 위치 개선

imageList 상태 초기화 위치를 다른 상태들과 함께 배치한 것은 코드 구조 개선에 도움이 됩니다.


135-135: FIXME 주석 처리 필요

"[x]FIXME: div 컴포넌트 수정" 주석이 있습니다. 이 주석은 PR 제출 전에 해결되거나 제거되어야 합니다. 또는 해당 작업을 추적할 수 있는 이슈를 생성하는 것이 좋습니다.

해당 FIXME가 이번 PR에서 해결해야 할 작업인지, 아니면 향후 작업을 위한 메모인지 확인해주세요.


211-211: 헤딩 계층 구조 개선

"활동 사진 편집" 섹션의 헤딩을 <h3>로 변경한 것은 페이지의 계층 구조를 일관되게 유지하는 데 도움이 됩니다.

frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.tsx (1)

1-166: 전체적으로 잘 구현된 컴포넌트입니다.

코드가 잘 구조화되어 있고, 적절한 오류 처리와 반응형 디자인이 포함되어 있습니다. usePhotoSwipeusePhotoNavigation 훅을 분리하여 관심사 분리도 잘 되어 있습니다. LazyImage 컴포넌트를 사용한 최적화도 좋은 접근입니다.

frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.styles.ts (1)

3-151: 전반적으로 잘 구현된 스타일 컴포넌트입니다.

스타일 컴포넌트가 전체적으로 잘 구성되어 있으며, 반응형 디자인도 적절하게 구현되어 있습니다. 각 컴포넌트의 역할이 명확하며, 모바일과 데스크톱 환경 모두를 고려한 설계가 돋보입니다.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 26, 2025

@seongwon030 PR 리뷰를 시작하겠습니다. 요청해주셔서 감사합니다.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 26, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

이번 PR에서는 다양한 컴포넌트와 커스텀 훅이 추가되고, 일부 기존 로직이 제거 및 리팩토링되었습니다.
새로운 LazyImage 컴포넌트가 추가되어 IntersectionObserver를 통한 레이지 로딩을 구현하였고, 쿼리 관련 후크에서는 캐시 무효화 로직이 제거되었습니다.
또한, 사진 갤러리 관리를 위한 usePhotoNavigationusePhotoSwipe 훅이 도입되었고, AdminPage 및 ClubDetailPage의 이미지 업로드/미리보기와 활동사진용 PhotoList 컴포넌트 관련 타입 정의 및 스타일이 업데이트되었습니다.

Changes

File(s) Change Summary
frontend/src/hooks/LazyImage.tsx LazyImage 컴포넌트 추가 (IntersectionObserver를 사용한 레이지 로딩 구현).
frontend/src/hooks/queries/club/useUpdateClubDescription.ts, frontend/src/hooks/queries/club/useUpdateClubDetail.ts useQueryClient 및 성공 핸들러 제거, 캐시 무효화 로직 삭제.
frontend/src/hooks/usePhotoNavigation.ts, frontend/src/hooks/usePhotoSwipe.ts 사진 갤러리 내비게이션 및 터치 스와이프 관리를 위한 새로운 커스텀 훅 추가.
frontend/src/pages/AdminPage/components/ImagePreview/ImagePreview.tsx, frontend/src/pages/AdminPage/components/ImageUpload/ImageUpload.tsx 이미지 미리보기 및 업로드 컴포넌트의 prop 타입 정의를 중앙화된 인터페이스(ImagePreviewProps, ImageUploadProps)로 업데이트.
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts, frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx 레이아웃 간격 조정 및 import, state 선언 재구성.
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx, frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.tsx, frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.styles.ts, frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.styles.ts 활동사진용 PhotoList 컴포넌트 추가 및 반응형 스타일 적용. (경로 간소화: frontend/src/pages/ClubDetailPage/...)
frontend/src/types/club.ts, frontend/src/utils/convertGoogleDriveUrl.ts 이미지 관련 인터페이스 추가 및 Google Drive URL 변환 유틸리티 함수 도입.

Sequence Diagram(s)

sequenceDiagram
    participant U as 사용자
    participant PL as PhotoList
    participant PS as usePhotoSwipe
    participant PN as usePhotoNavigation
    participant LI as LazyImage

    U->>PL: 사진 스와이프 시작
    PL->>PS: 터치 이벤트 처리 (handleTouchStart/Move/End)
    PS->>PL: translateX 업데이트
    PL->>PN: 내비게이션 상태 요청
    PN->>PL: 이전/다음 인덱스 반환
    PL->>LI: 현재 사진 렌더링 요청
    LI-->>PL: 이미지 레이지 로딩 처리
Loading

Assessment against linked issues

Objective Addressed Explanation
상세페이지에 활동사진 컴포넌트 추가 [FE-45]

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🔭 Outside diff range comments (1)
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (1)

50-53: 🛠️ Refactor suggestion

이미지 추가 함수에서 상태 업데이트 최적화

addImage 함수에서는 API 호출 결과를 기다리지 않고 바로 상태를 업데이트하고 있습니다. API 호출이 실패하면 UI와 실제 데이터가 불일치할 수 있습니다. API 호출 성공 후 상태를 업데이트하는 것이 좋습니다.

 const addImage = (newImage: string) => {
-  updateFeedImages({ feeds: [...imageList, newImage], clubId: TEMP_CLUB_ID });
-  setImageList([...imageList, newImage]);
+  const newImageList = [...imageList, newImage];
+  updateFeedImages(
+    { feeds: newImageList, clubId: clubDetail?.id || TEMP_CLUB_ID },
+    {
+      onSuccess: () => {
+        setImageList(newImageList);
+      },
+      onError: (error) => {
+        console.error('이미지 업데이트 실패:', error);
+        // 에러 처리 로직 (예: 사용자에게 알림)
+      }
+    }
+  );
 };
🧹 Nitpick comments (14)
frontend/src/utils/convertGoogleDriveUrl.ts (1)

1-15: 구글 드라이브 URL 변환 로직이 효과적으로 구현되었습니다.

함수가 정상적으로 구글 드라이브 URL에서 파일 ID를 추출하고 썸네일 URL로 변환하는 작업을 수행합니다. 정규식 패턴과 에러 처리도 잘 되어 있습니다.

다만, 몇 가지 개선 사항을 고려해볼 수 있습니다:

  1. 썸네일 크기가 w2048로 하드코딩되어 있어 다양한 사용 사례에 맞게 크기를 선택할 수 있도록 매개변수화하는 것이 좋을 수 있습니다.
  2. 정규식이 일반적인 구글 드라이브 URL 패턴을 다루고 있지만, 다른 형식의 URL(예: 공유 링크)도 처리할 수 있는지 확인해보세요.
-const convertGoogleDriveUrl = (url: string) => {
+const convertGoogleDriveUrl = (url: string, size: string = 'w2048') => {
  try {
    const fileIdMatch = url.match(/(?:id=|\/d\/)([-\w]{25,})/);
    if (!fileIdMatch) return url;

    const fileId = fileIdMatch[1];

-    return `https://drive.google.com/thumbnail?id=${fileId}&sz=w2048`;
+    return `https://drive.google.com/thumbnail?id=${fileId}&sz=${size}`;
  } catch (error) {
    console.error('URL 변환 중 오류 발생:', error);
    return url;
  }
};
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

3-13: 임포트 순서가 재정렬되었습니다.

임포트 순서 변경은 기능에 영향을 주지 않지만, 일관된 임포트 순서를 유지하는 것이 코드 가독성을 향상시킵니다. 프로젝트 전체에서 임포트 순서에 대한 일관된 규칙을 따르는 것이 좋습니다.

frontend/src/hooks/LazyImage.tsx (2)

15-31: IntersectionObserver 사용 최적화

threshold 값을 props로 받아서 사용자가 조정할 수 있게 하는 것이 좋습니다. 또한 브라우저 호환성 문제를 고려하여 polyfill이나 fallback 메커니즘을 추가하는 것이 좋습니다.

 const LazyImage = ({
   src,
   alt,
   onError,
+  threshold = 0.1,
 }: {
   src: string;
   alt: string;
   onError?: () => void;
+  threshold?: number;
 }) => {
   // ...
   useEffect(() => {
     const observer = new IntersectionObserver(
       ([entry]) => {
         if (entry.isIntersecting) {
           setIsVisible(true);
           observer.disconnect();
         }
       },
-      { threshold: 0.1 },
+      { threshold },
     );
     // ...
   }, []);

33-37: 로딩 상태 관리 추가 고려

현재 이미지가 로딩되는 동안의 상태를 관리하지 않고 있습니다. 이미지 로딩 중에 로딩 인디케이터나 스켈레톤 UI를 표시하는 것이 사용자 경험을 향상시킬 수 있습니다.

 const LazyImage = ({
   src,
   alt,
   onError,
 }: {
   src: string;
   alt: string;
   onError?: () => void;
 }) => {
   const [isVisible, setIsVisible] = useState(false);
+  const [isLoaded, setIsLoaded] = useState(false);
   const imgRef = useRef<HTMLImageElement | null>(null);
   
   // ...
   
   return isVisible ? (
-    <img ref={imgRef} src={src} alt={alt} onError={onError} />
+    <>
+      {!isLoaded && <div className="loading-indicator" />}
+      <img
+        ref={imgRef}
+        src={src}
+        alt={alt}
+        onLoad={() => setIsLoaded(true)}
+        onError={() => {
+          setIsLoaded(true);
+          onError && onError();
+        }}
+        style={{ display: isLoaded ? 'block' : 'none' }}
+      />
+    </>
   ) : (
     <div ref={imgRef} style={{ width: '100%', height: '100%' }} />
   );
frontend/src/hooks/usePhotoNavigation.ts (2)

19-22: 명확한 변수명과 문서화 필요

isLastCardInMiddleisSecondLastCardAtStart 변수는 이해하기 어려운 복잡한 조건을 가지고 있습니다. 이러한 변수들에 대한 설명을 주석으로 추가하거나, 더 명확한 이름으로 변경하는 것이 좋습니다.


1-64: 창 크기 변경에 대한 처리 부족

현재 훅은 컨테이너나 카드 크기가 변경될 때 대응하는 로직이 없습니다. 이는 반응형 UI에서 문제가 될 수 있습니다. 상위 컴포넌트에서 창 크기 변경 이벤트를 처리하고 필요한 값을 업데이트하는 것이 필요합니다.

frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (1)

135-135: FIXME 주석 구체화 필요

코드에 "div 컴포넌트 수정" FIXME 주석이 있지만, 어떤 문제가 있는지, a어떻게 수정해야 하는지 명확하지 않습니다. 주석에 더 많은 정보를 추가하는 것이 좋습니다.

-// [x]FIXME: div 컴포넌트 수정
+// [x]FIXME: div 컴포넌트를 의미론적으로 더 적절한 컴포넌트로 수정하고, 스타일 개선 필요
frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.tsx (2)

13-24: 컴포넌트 설계가 잘 되어 있지만, cardWidth 계산 방식을 개선할 수 있습니다.

컴포넌트의 기본 구조와 props 정의가 잘 되어 있습니다. 다만, 카드 너비(cardWidth)가 하드코딩되어 있어 스타일이 변경될 경우 일관성을 유지하기 어려울 수 있습니다.

- const cardWidth = 428; // 400px + 28px gap
+ const CARD_WIDTH = 400;
+ const CARD_GAP = 28;
+ const cardWidth = CARD_WIDTH + CARD_GAP;

이렇게 수정하면 카드 넓이와 간격을 분리하여 관리할 수 있고, 이후 스타일 변경 시 일관성을 유지하기 쉬워집니다.


109-162: 컴포넌트 렌더링 및 접근성 개선이 필요합니다.

전체적인 컴포넌트 렌더링 로직은 잘 구현되어 있으나, 접근성(a11y) 측면에서 개선이 필요합니다. 네비게이션 버튼에 적절한 aria 속성이 없고, 키보드 접근성 지원이 부족합니다.

  <Styled.NavigationButton direction='left' onClick={handlePrev}>
-
+   <span aria-hidden="true">←</span>
+   <span className="sr-only">이전 사진</span>
  </Styled.NavigationButton>

  <Styled.NavigationButton direction='right' onClick={handleNext}>
-
+   <span aria-hidden="true">→</span>
+   <span className="sr-only">다음 사진</span>
  </Styled.NavigationButton>

추가로, styled-components에 다음과 같은 스타일을 추가하면 좋겠습니다:

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

또한 키보드 탐색을 위한 keydown 이벤트 핸들러도 추가하는 것이 좋겠습니다.

frontend/src/hooks/usePhotoSwipe.ts (3)

16-31: 훅 구현에 한글 주석이 포함되어 있습니다.

코드에 한글 주석이 포함되어 있습니다. 프로젝트의 주석 컨벤션에 따라 영어로 통일하거나, 팀 내 합의된 언어로 주석을 작성하는 것이 좋습니다.

- isLastCardInMiddle, // 	마지막 카드를 절반만 보여주는 상태인지
- isSecondLastCardAtStart, // 마지막에서 2번째 카드가 가장 왼쪽에 있을 때인지
+ isLastCardInMiddle, // If the last card is only partially visible
+ isSecondLastCardAtStart, // If the second-to-last card is at the leftmost position

또한 상태 관리를 위한 초기 값들이 잘 설정되어 있습니다.


38-68: 터치 이동 처리에 하드코딩된 반응형 값이 있습니다.

터치 이동 처리 로직에 window.innerWidth를 직접 확인하는 코드가 있습니다. 이러한 방식은 커스텀 훅이나 Context를 통해 관리하는 것이 더 유지보수하기 좋습니다.

- const maxSlideDistance = window.innerWidth <= 500 ? 100 : cardWidth;
+ const isMobile = window.innerWidth <= 500;
+ const maxSlideDistance = isMobile ? 100 : cardWidth;

또한 62번 라인의 하드코딩된 값(400)도 상수로 관리하는 것이 좋습니다:

  currentIndex === photosLength - 1 &&
- newTranslate < containerWidth - 400
+ newTranslate < containerWidth - CARD_WIDTH

이렇게 하면 코드의 일관성과 유지보수성이 향상됩니다.


70-93: 터치 종료 처리에도 하드코딩된 반응형 값과 임계값이 있습니다.

터치 종료 처리 로직에도 window.innerWidth를 직접 체크하는 코드가 있습니다. 앞서 제안한 것처럼 이러한 값들은 상수나 설정으로 관리하는 것이 좋습니다.

- const threshold = window.innerWidth <= 500 ? 50 : 100;
+ const MOBILE_THRESHOLD = 50;
+ const DESKTOP_THRESHOLD = 100;
+ const threshold = window.innerWidth <= 500 ? MOBILE_THRESHOLD : DESKTOP_THRESHOLD;

전반적으로 터치 이벤트 처리 로직은 잘 구현되어 있지만, 마지막 스와이프 조건이 복잡하여 주석으로 설명을 추가하면 더 이해하기 쉬울 것입니다.

frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.styles.ts (2)

78-109: NavigationButton에 주석 처리된 코드가 있습니다.

NavigationButton 스타일에 주석 처리된 z-index 속성이 있습니다. 불필요한 주석은 제거하는 것이 좋습니다.

  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- /* z-index: 2; */
  transition: all 0.3s ease;

전반적으로 네비게이션 버튼 스타일은 호버 효과를 포함하여 잘 구현되어 있습니다.


111-141: ProgressBar가 모바일에서 숨겨지는 문제가 있습니다.

ProgressBar 컴포넌트가 모바일 뷰(500px 이하)에서 표시되지 않습니다. 모바일 사용자에게도 진행 상황을 제공하는 것이 좋을 수 있습니다.

모바일에서도 진행 상황을 표시하는 방법을 고려해보세요. 예를 들어, 점(dot) 형태의 인디케이터로 대체하거나, 간소화된 형태의 진행 막대를 제공할 수 있습니다.

export const ProgressBar = styled.div<{ progress: number }>`
  width: ${({ progress }) => progress}%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.2);
  transition: width 0.3s ease;

  @media (max-width: 500px) {
-   display: none;
+   height: 4px;
+   background-color: rgba(0, 0, 0, 0.3);
  }
`;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 292975a and 1880f06.

📒 Files selected for processing (15)
  • frontend/src/hooks/LazyImage.tsx (1 hunks)
  • frontend/src/hooks/queries/club/useUpdateClubDescription.ts (1 hunks)
  • frontend/src/hooks/queries/club/useUpdateClubDetail.ts (1 hunks)
  • frontend/src/hooks/usePhotoNavigation.ts (1 hunks)
  • frontend/src/hooks/usePhotoSwipe.ts (1 hunks)
  • frontend/src/pages/AdminPage/components/ImagePreview/ImagePreview.tsx (1 hunks)
  • frontend/src/pages/AdminPage/components/ImageUpload/ImageUpload.tsx (2 hunks)
  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts (1 hunks)
  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (3 hunks)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2 hunks)
  • frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.tsx (1 hunks)
  • frontend/src/types/club.ts (1 hunks)
  • frontend/src/utils/convertGoogleDriveUrl.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (3)
frontend/src/pages/AdminPage/components/ImageUpload/ImageUpload.tsx (1)
frontend/src/types/club.ts (1)
  • ImageUploadProps (27-30)
frontend/src/pages/AdminPage/components/ImagePreview/ImagePreview.tsx (1)
frontend/src/types/club.ts (1)
  • ImagePreviewProps (32-35)
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (3)
frontend/src/types/club.ts (1)
  • ClubDetail (12-20)
frontend/src/apis/updateFeedImages.ts (1)
  • updateFeedImages (3-19)
frontend/src/hooks/queries/club/useUpdateFeedImages.ts (1)
  • useUpdateFeedImages (4-9)
🔇 Additional comments (23)
frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.styles.ts (1)

19-19: 모바일 화면에서의 시각적 구분선이 추가되었네요!

모바일 화면(500px 이하)에서 IntroduceBoxWrapper에 하단 테두리를 추가하여 컴포넌트 간의 시각적 구분을 개선했습니다. 이렇게 하면 모바일 환경에서 콘텐츠 영역이 더 명확하게 구분되어 사용자 경험이 향상될 것입니다.

frontend/src/hooks/queries/club/useUpdateClubDetail.ts (1)

1-14:

Details

❓ Verification inconclusive

queryClient 캐시 무효화 로직이 제거되었습니다.

useQueryClient와 onSuccess 콜백에서의 queryClient.invalidateQueries(['clubDetail']) 로직이 제거되었습니다. 이는 의도적인 변경인지 확인이 필요합니다. 해당 로직이 제거됨으로써 클럽 정보가 업데이트된 후 캐시가 자동으로 갱신되지 않을 수 있습니다.

다음을 확인해주세요:

  1. 다른 곳에서 캐시를 갱신하는 로직이 구현되어 있는지
  2. 데이터 업데이트 후 사용자에게 최신 데이터가 정확히 보여지는지
  3. 이러한 변경이 PR 목적(활동사진 추가)과 연관이 있는지

🏁 Script executed:

#!/bin/bash
# 다른 파일에서 clubDetail 쿼리 캐시 무효화 처리 여부 확인
rg "invalidateQueries\(\['clubDetail'\]\)" --type ts -g "!*.test.ts" -g "!*/node_modules/*"

Length of output: 99


캐시 무효화 로직 재검증 필요

  • 현재 파일(frontend/src/hooks/queries/club/useUpdateClubDetail.ts)에서 queryClient 캐시 무효화 로직이 제거된 상태입니다.
  • 코드베이스 전체에서 invalidateQueries(['clubDetail']) 또는 queryClient.invalidateQueries 관련 코드가 검색되지 않아, 다른 곳에서 캐시를 갱신하는 로직이 구현되어 있지 않은 것으로 보입니다.
  • 데이터 업데이트 후 클럽 정보가 최신 데이터로 정확히 반영되는지, 그리고 해당 변경사항이 '활동사진 추가' PR 목적과 연관된 의도적인 변경인지 추가 확인이 필요합니다.

해당 사항에 대해 직접 재검증해주시기 바랍니다.

frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts (1)

102-107: ImageContainer의 간격 조정 및 margin-top 제거

ImageContainer 컴포넌트의 gap이 변경되었고, margin-top 속성이 제거된 것으로 보입니다. 이러한 변경은 이미지 컨테이너의 레이아웃을 더 조밀하게 만들고, 상위 요소와의 간격을 없애는 효과가 있습니다.

해당 변경이 활동사진 추가 기능과 연관된 UI 개선이라면 적절한 변경으로 보입니다.

frontend/src/hooks/queries/club/useUpdateClubDescription.ts (1)

1-14:

Details

❓ Verification inconclusive

queryClient 캐시 무효화 로직이 제거되었습니다.

useUpdateClubDetail과 마찬가지로, 이 훅에서도 useQueryClient와 onSuccess 콜백에서의 queryClient.invalidateQueries(['clubDetail']) 로직이 제거되었습니다. 이는 데이터 갱신 방식에 대한 의도적인 변경인지 확인이 필요합니다.

해당 변경이 의도적이라면 다음 사항들을 고려해야 합니다:

  1. 데이터 변경 후 UI가 즉시 업데이트되는지 확인
  2. 상태 관리 방식이 변경되었다면, 해당 변경에 맞게 다른 관련 훅들도 업데이트 필요
  3. 캐시 갱신이 어떻게 처리되는지 문서화 필요

🏁 Script executed:

#!/bin/bash
# 관련 API 호출 이후 데이터 갱신 처리 방식 확인
rg "updateClubDescription|updateClubDetail" --type ts -A 10 -B 5 -g "!*.test.ts" -g "!*/node_modules/*"

Length of output: 7734


캐시 무효화 로직 제거 변경 사항 확인 및 문서화 요청

현재 useUpdateClubDescription 훅에서 기존에 사용되던 useQueryClientonSuccess에서의 queryClient.invalidateQueries(['clubDetail']) 로직이 제거된 것을 확인했습니다. RecruitEditTab.tsx에서 해당 훅 호출 시 직접적으로 상태 업데이트(setDescription)를 통해 UI 갱신을 처리하고 있는 점을 볼 수 있습니다.

변경이 의도적이라면 아래 사항을 확인 및 문서화해주시기 바랍니다:

  • 데이터 변경 후 UI가 즉시 업데이트 되는지 (수동 상태 업데이트 또는 다른 캐시 갱신 처리 방식을 통해)
  • 상태 관리 방식 변경에 따라, 관련된 다른 훅들(예: useUpdateClubDetail)에도 동일한 변경이 적용되었는지
  • 변경된 캐시 갱신 처리 방식에 대한 이유와 동작 방식의 명확한 문서화
frontend/src/types/club.ts (1)

26-35: 타입 정의가 명확하게 구현되었습니다.

두 인터페이스(ImageUploadPropsImagePreviewProps)가 각각의 컴포넌트에 필요한 속성을 잘 정의하고 있습니다. 이러한 타입 분리를 통해 코드 재사용성과 타입 안전성이 향상됩니다.

frontend/src/pages/AdminPage/components/ImageUpload/ImageUpload.tsx (2)

5-10: 타입 임포트 및 적용이 적절하게 이루어졌습니다.

ImageUploadProps 인터페이스를 가져와서 컴포넌트 프롭스에 적용한 것은 코드 가독성과 유지보수성을 향상시킵니다. 인라인 타입 정의 대신 중앙화된 타입을 사용함으로써 일관된 타입 관리가 가능해졌습니다.


40-40: default export로 변경된 점이 적절합니다.

명명된 내보내기(named export)에서 기본 내보내기(default export)로 변경함으로써 임포트 시 더 간결한 구문을 사용할 수 있게 되었습니다.

frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

60-60:

Details

✅ Verification successful

PhotoList 컴포넌트가 적절하게 추가되었습니다.

ClubDetailPage에 PhotoList 컴포넌트를 추가하여 활동 사진을 표시하는 기능이 잘 구현되었습니다. 컴포넌트에 필요한 props인 sectionRefsfeeds가 적절하게 전달되고 있습니다.

구글 드라이브 URL 변환 유틸리티를 이 컴포넌트에서 사용하는지 확인해보는 것이 좋을 것 같습니다. 만약 PhotoListfeeds 배열의 각 항목에 대해 구글 드라이브 URL을 처리한다면, 변환 로직이 올바르게 적용되고 있는지 확인하세요.


🏁 Script executed:

#!/bin/bash
# PhotoList 컴포넌트에서 Google Drive URL 변환 유틸리티 사용 확인
echo "PhotoList 컴포넌트 코드 확인:"
fd "PhotoList.tsx" | grep -r "frontend/src/pages/ClubDetailPage/components/PhotoList" | xargs cat

echo -e "\nPhotoList에서 convertGoogleDriveUrl 임포트 확인:"
fd "PhotoList.tsx" | grep -r "frontend/src/pages/ClubDetailPage/components/PhotoList" | xargs grep -r "convertGoogleDriveUrl" || echo "PhotoList에서 convertGoogleDriveUrl을 임포트하지 않음"

Length of output: 925


PhotoList 컴포넌트 및 URL 변환 유틸리티 정상 작동 확인

ClubDetailPage에 전달된 sectionRefsfeeds props를 통해 PhotoList 컴포넌트가 올바르게 구현되었으며, 내부에서 @/utils/convertGoogleDriveUrl을 임포트하여 구글 드라이브 URL 변환 로직이 정확하게 적용되었습니다. 모든 확인 결과, 관련 기능은 의도한 대로 동작하고 있음을 확인했습니다.

frontend/src/pages/AdminPage/components/ImagePreview/ImagePreview.tsx (1)

3-5: 타입 개선이 잘 이루어졌습니다.

인터페이스를 중앙 집중화하여 타입 안정성과 유지보수성을 개선한 것은 좋은 리팩토링입니다.

frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (1)

211-211: 제목 계층 구조 개선이 잘 이루어졌습니다.

<h3> 태그를 사용하여 일관된 제목 계층 구조를 유지한 것은, 시맨틱 HTML 관점에서 좋은 변경입니다.

frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.tsx (5)

25-31: Google Drive URL 변환 로직이 적절히 구현되어 있습니다.

useMemo를 사용하여 불필요한 재계산을 방지하고, 조건부 체크를 통해 안전하게 배열을 처리하는 점이 좋습니다.


33-46: 윈도우 이벤트 리스너 정리를 적절히 처리하고 있습니다.

컴포넌트 마운트 시 컨테이너 너비를 업데이트하고, 리사이즈 이벤트를 적절히 관리하고 있습니다. useEffect 클린업 함수를 통해 이벤트 리스너가 정상적으로 제거되어 메모리 누수를 방지합니다.


48-65: 커스텀 훅을 활용한 관심사 분리가 잘 되어 있습니다.

usePhotoNavigation 훅을 사용하여 탐색 로직을 컴포넌트에서 분리한 접근 방식이 좋습니다. 이를 통해 컴포넌트 코드가 더 깔끔해지고 유지보수성이 향상됩니다.


66-78: usePhotoSwipe 훅을 효과적으로 사용하고 있습니다.

터치 이벤트 관련 로직을 커스텀 훅으로 분리하여 코드의 가독성과 재사용성을 높였습니다.


103-105: 이미지 에러 처리 로직이 잘 구현되어 있습니다.

이미지 로드 실패를 적절히 처리하는 함수입니다. 에러가 발생한 이미지 인덱스를 기록하여 대체 UI를 표시하는 방식이 좋습니다.

frontend/src/hooks/usePhotoSwipe.ts (3)

3-14: 인터페이스 정의가 명확하고 타입이 잘 지정되어 있습니다.

UsePhotoSwipeProps 인터페이스가 잘 정의되어 있어 훅의 사용 방법을 명확히 이해할 수 있습니다.


32-36: 터치 시작 이벤트 처리가 적절합니다.

터치 이벤트 시작 시 현재 상태를 캡처하는 로직이 잘 구현되어 있습니다.


95-101: 훅의 반환값이 명확하게 정의되어 있습니다.

훅이 반환하는 값들이 명확하게 정의되어 있어서 사용하기 쉽습니다.

frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.styles.ts (5)

3-19: PhotoListContainer 스타일이 적절하게 정의되어 있습니다.

반응형 디자인이 잘 구현되어 있으며, 모바일 뷰에서 적절하게 스타일이 변경됩니다.


21-26: PhotoListWrapper 스타일이 간결하게 정의되어 있습니다.

오버플로우 처리와 터치 동작이 적절하게 설정되어 있습니다.


28-54: PhotoList 스타일이 복잡한 조건부 로직을 포함하고 있습니다.

PhotoList 컴포넌트에 다양한 props를 받아 처리하는 로직이 있습니다. 조건부 스타일링이 복잡해질 경우, 이를 별도의 유틸리티 함수로 분리하는 것을 고려해 볼 수 있습니다.


56-76: PhotoCard 스타일이 잘 정의되어 있습니다.

이미지 카드의 스타일이 잘 정의되어 있으며, 모바일 뷰에서도 적절하게 조정됩니다.


143-151: NoImageContainer가 적절하게 구현되어 있습니다.

이미지가 없을 때 표시되는 컨테이너 스타일이 잘 정의되어 있습니다.

Copy link
Member

@oesnuj oesnuj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

활동 사진 편집하시느라 정말 수고 많으셨습니다!

관리자 페이지의 recruitEdit 수정 기능이 여기 PR에 있었군요!
이거 머지되면 드디어 관리자 페이지가 완성될 것 같네요 😊

- 사진 리스트 끝에 가짜 카드를 추가하여 레이아웃을 유지하도록 수정.
- `usePhotoNavigation`에서 가짜 카드까지 슬라이드가 가능하도록 로직 수정.
- `PhotoCard` 스타일에 `isPlaceholder` 속성을 추가하여 가짜 카드를 숨김 처리.
- 모바일 환경에서 카드 크기와 슬라이드 동작이 올바르게 작동하도록 조정.
- calculateTranslateX로 슬라이드 함수 분리
- 필요없는 상수 제거
- mobile상태 제거
@seongwon030 seongwon030 merged commit d088f05 into develop/fe Apr 2, 2025
5 checks passed
@seongwon030 seongwon030 deleted the feature/#235-add-photo-component-FE-45 branch April 2, 2025 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] FE-45 상세페이지에 활동사진 컴포넌트 추가

2 participants