Skip to content

Conversation

@BokyungCodes
Copy link
Contributor

@BokyungCodes BokyungCodes commented Aug 3, 2025

📌 변경 사항 개요

  • 팀원들과 1,2차 QA 후 나온 메인페이지 이슈 해결

📝 상세 내용

  • 메인페이지 카드 섹션 타이틀 글자수 제한
  • 메인페이지 카테고리 부분 pc에서 화면 줄였을 때 스크롤 작동 안됨
  • 인기 체험 섹션 연속 클릭시 이동되는 칸 범위가 달라짐
  • 인기 체험 섹션 캐러셀 화살표 여러번 클릭 시 파란색 반전 영역 없애기
  • 닉네임 변경 후 헤더에 실시간 반영
  • 체험 등록 후 메인 리스트 반영 (invalidate)
  • 체험 삭제 후 메인 리스트 반영 (invalidate)
  • 모바일에서 푸터의 '코드잇' 부분 텍스트 수정
  • 알림 팝업 브라우저 크기에 맞춰 위치 이동
  • 체험 등록/수정 페이지 모바일 레이아웃 전체적인 수정
  • 메인페이지 전체적인 css 수정

🔗 관련 이슈

🖼️ 스크린샷(선택사항)

💡 참고 사항

  • 메인 페이지에 호버 및 모션 효과 추가

Summary by CodeRabbit

  • 신규 기능

    • 인기 체험 목록에 무한 스크롤 기능이 추가되어 더 많은 체험을 연속적으로 탐색할 수 있습니다.
    • 카테고리 필터에서 마우스 드래그로 가로 스크롤이 가능하며, 스크롤 가능 여부를 시각적으로 안내합니다.
  • 개선 및 변경

    • 주요 섹션 및 체험 카드에 애니메이션 효과와 호버 인터랙션이 추가되어 더욱 생동감 있는 UI를 제공합니다.
    • 드롭다운, 필터, 입력 폼 등 여러 UI 요소의 스타일과 레이아웃이 개선되고, 드롭다운은 외부 스타일 커스터마이즈가 가능해졌습니다.
    • 인기 체험, 경험 목록, 마이페이지 등 다양한 컴포넌트의 패딩, 정렬, 텍스트 클램프 등 세부 스타일이 조정되었습니다.
    • 프로필 수정 시 헤더의 사용자 정보가 즉시 동기화됩니다.
    • 푸터 및 헤더 메뉴의 간격과 위치 조정으로 반응형 UI가 향상되었습니다.
    • 텍스트 줄임(ellipsis)용 커스텀 유틸리티 클래스가 추가되어 다양한 텍스트 클램프 스타일을 지원합니다.
  • 버그 수정

    • 없음
  • 기타

    • 없음

@coderabbitai
Copy link

coderabbitai bot commented Aug 3, 2025

Walkthrough

이 변경 사항은 메인 페이지의 UI/UX 개선, 인기 체험 무한 스크롤, 드롭다운 커스텀 스타일링, 카테고리 필터 드래그 스크롤, 카드 타이틀 줄임 처리, 쿼리 무효화 로직, 헤더/푸터/프로필 드롭다운 스타일, 글로벌 유저 상태 동기화 등 다양한 컴포넌트와 스타일에 걸쳐 다수의 UI 및 UX 관련 수정을 포함합니다.

Changes

Cohort / File(s) Change Summary
BannerSection 스타일 개선
src/app/(with-header)/components/BannerSection.tsx
텍스트 컨테이너의 고정 폭(w-220) 클래스 제거, 레이아웃 및 스타일 유지
BasePage 애니메이션 추가
src/app/(with-header)/components/BasePage.tsx
주요 섹션(BannerSection, ExperienceList, PopularExperiences)에 framer-motion 애니메이션 래퍼 적용, 시차 애니메이션 구현
카테고리 필터 드래그 스크롤
src/app/(with-header)/components/CategoryFilter.tsx
마우스 드래그 스크롤 및 스크롤 가능성 시각 피드백 추가, 커서/선택 스타일 개선
체험 카드 인터랙션 및 줄임 처리
src/app/(with-header)/components/ExperienceCard.tsx, src/app/(with-header)/components/PopularCard.tsx, src/app/globals.css
카드 hover 애니메이션, 이미지 확대, 타이틀 margin/padding 조정, 커스텀 줄임(line-clamp-2-custom) 클래스 적용 및 글로벌 유틸리티 CSS 추가
체험 리스트 UI 개선 및 드롭다운 커스텀
src/app/(with-header)/components/ExperienceList.tsx, src/components/Dropdown.tsx, src/types/dropdownTypes.ts
필터바/헤더/카드 컨테이너 패딩 및 정렬 개선, 드롭다운에 커스텀 클래스 prop 추가, 드롭다운 체크 아이콘 렌더링 코드 주석 처리, 드롭다운 타입 정의 확장
인기 체험 무한 스크롤 및 API 페이징
src/app/(with-header)/components/PopularExperiences.tsx, src/app/api/experiences/getPopularExperiences.ts
인기 체험 목록에 useInfiniteQuery 적용, IntersectionObserver로 무한 스크롤 구현, API에 커서 기반 페이징 및 응답 타입/필드 확장
마이 액티비티/예약 폼 스타일 조정
src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx, src/app/(with-header)/myactivity/components/ReservationForm.tsx, src/app/(with-header)/myactivity/components/ScheduleSelect.tsx
폼 컨테이너 패딩/레이아웃/버튼 스타일 등 UI 개선, 반응형 레이아웃 적용
카테고리 인풋 커스텀 아이콘
src/app/(with-header)/myactivity/components/CategoryInput.tsx
기본 select 화살표 제거, 커스텀 ChevronIcon SVG 적용, 선택 상태에 따른 색상 변경, 아이콘 absolute 배치
체험 등록/수정 쿼리 무효화
src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts, src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts
체험 등록/수정 성공 시 관련 쿼리(experiences, popularExperiences)도 무효화하여 리스트 동기화
헤더/프로필/푸터 스타일 개선
src/components/Header.tsx, src/components/ProfileDropdown.tsx, src/components/Footer.tsx
메뉴/드롭다운/푸터 gap 및 정렬, z-index, copyright 텍스트 등 스타일
마이페이지 유저 상태 동기화
src/hooks/useMyPageQueries.ts
프로필 수정 시 글로벌 유저 상태(authStore)와 동기화, setGlobalUser 호출 추가

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CategoryFilter
  participant ExperienceList
  participant PopularExperiences
  participant API

  User->>CategoryFilter: 마우스 드래그로 스크롤 시도
  CategoryFilter->>CategoryFilter: 스크롤 위치 조정, gradient 숨김

  User->>ExperienceList: 정렬/카테고리 선택
  ExperienceList->>Dropdown: 드롭다운 선택
  Dropdown->>ExperienceList: 정렬 옵션 전달

  User->>PopularExperiences: 스크롤하여 sentinel 노출
  PopularExperiences->>API: getPopularExperiences(cursorId)
  API-->>PopularExperiences: activities, cursorId, totalCount
  PopularExperiences->>PopularExperiences: 데이터 누적 및 렌더링

  User->>EditActivityForm: 체험 수정 제출
  EditActivityForm->>useEditActivityForm: mutate
  useEditActivityForm->>API: updateActivity
  API-->>useEditActivityForm: 성공 응답
  useEditActivityForm->>QueryClient: ['activity', id], ['experiences'], ['popularExperiences'] 쿼리 무효화
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related issues

Possibly related PRs

Suggested labels

🔨 Refactor

Suggested reviewers

  • minimo-9
  • evaain706

Poem

🐇
오늘도 토끼는 코딩을 해,
스크롤도 드래그도 부드럽게!
카드 타이틀 줄임표,
인기 체험 무한 행렬,
드롭다운엔 멋진 옷을 입히고,
헤더엔 닉네임이 반짝,
"No Sleep, No Bugs!" 토끼의 자랑!
🥕✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f926162 and 95fab3a.

📒 Files selected for processing (1)
  • src/components/Header.tsx (2 hunks)
🔇 Additional comments (1)
src/components/Header.tsx (1)

78-78: 모바일 구간 gap 축소 적용 👍

gap-8 md:gap-24로 반응형 간격을 세밀하게 조정한 변경이 UX 관점에서 타당해 보입니다.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/121

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
  • 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 explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @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.

@BokyungCodes BokyungCodes self-assigned this Aug 3, 2025
@BokyungCodes BokyungCodes added 🐞 bug 버그관련 🎨 style 마크업 및 스타일링 💻 chore 사소한 수정(빌드,패키지매니저등) 📬 API 서버 api 통신관련 ✨ Feature 기능구현 labels Aug 3, 2025
Copy link

@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: 17

🔭 Outside diff range comments (2)
src/hooks/useMyPageQueries.ts (2)

131-136: 코드 중복을 제거하세요.

useUploadProfileImage 함수에서 동일한 코드가 중복되어 있습니다.

다음 중복 코드를 제거하세요:

      setUser(updatedUser);
      queryClient.setQueryData(QUERY_KEYS.PROFILE, updatedUser);

      setLoading(false);
      setGlobalUser(updatedUser); // [추가] 헤더 상태(authStore)도 동기화

-     queryClient.setQueryData(QUERY_KEYS.PROFILE, updatedUser);
-
-     setLoading(false);
      alert('프로필 이미지가 성공적으로 업로드되었습니다!');

69-69: 사용자 경험 개선을 위해 알림 방식을 통일하세요.

alert() 대신 toast 알림을 사용하는 것을 고려해보세요. 이미 헤더에서 toast from 'sonner'를 사용하고 있어 일관성을 위해 동일한 방식을 적용하는 것이 좋겠습니다.

- alert('프로필이 성공적으로 업데이트되었습니다!');
+ toast.success('프로필이 성공적으로 업데이트되었습니다!');

Also applies to: 76-76, 136-136, 142-142

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4c9f0a3 and e9c62da.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • src/app/(with-header)/components/BannerSection.tsx (1 hunks)
  • src/app/(with-header)/components/BasePage.tsx (2 hunks)
  • src/app/(with-header)/components/CategoryFilter.tsx (2 hunks)
  • src/app/(with-header)/components/ExperienceCard.tsx (2 hunks)
  • src/app/(with-header)/components/ExperienceList.tsx (2 hunks)
  • src/app/(with-header)/components/PopularCard.tsx (1 hunks)
  • src/app/(with-header)/components/PopularExperiences.tsx (4 hunks)
  • src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx (1 hunks)
  • src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts (1 hunks)
  • src/app/(with-header)/myactivity/components/CategoryInput.tsx (2 hunks)
  • src/app/(with-header)/myactivity/components/ReservationForm.tsx (1 hunks)
  • src/app/(with-header)/myactivity/components/ScheduleSelect.tsx (4 hunks)
  • src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts (3 hunks)
  • src/app/api/experiences/getPopularExperiences.ts (1 hunks)
  • src/app/globals.css (1 hunks)
  • src/components/Dropdown.tsx (4 hunks)
  • src/components/Footer.tsx (1 hunks)
  • src/components/Header.tsx (2 hunks)
  • src/components/ProfileDropdown.tsx (1 hunks)
  • src/hooks/useMyPageQueries.ts (1 hunks)
  • src/types/dropdownTypes.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/hooks/useMyPageQueries.ts (1)
src/app/(with-header)/mypage/profile/page.tsx (1)
  • ProfilePage (14-183)
src/app/(with-header)/myactivity/components/ScheduleSelect.tsx (1)
src/app/(with-header)/myactivity/components/ScheduleSelectForm.tsx (3)
  • ScheduleSelectForm (17-60)
  • ScheduleSelectFormProps (6-15)
  • function (44-44)
src/app/api/experiences/getPopularExperiences.ts (3)
src/types/experienceListTypes.ts (2)
  • Experience (1-14)
  • ExperienceResponse (16-20)
src/apis/instance.ts (1)
  • instance (100-100)
src/app/api/experiences/getExperiences.ts (1)
  • ExperienceResponse (11-15)
src/app/(with-header)/components/BasePage.tsx (3)
src/app/(with-header)/components/BannerSection.tsx (1)
  • BannerSection (9-39)
src/app/(with-header)/components/ExperienceList.tsx (1)
  • ExperienceList (25-125)
src/app/(with-header)/components/PopularExperiences.tsx (1)
  • PopularExperiences (16-119)
🪛 Biome (2.1.2)
src/components/ProfileDropdown.tsx

[error] 58-63: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 58-63: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

src/app/(with-header)/components/CategoryFilter.tsx

[error] 64-74: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🔇 Additional comments (24)
src/app/(with-header)/components/BannerSection.tsx (1)

25-25: 반응형 디자인 개선을 승인합니다.

고정 너비 클래스 w-220을 제거하고 반응형 클래스를 유지하는 것이 적절합니다. 이를 통해 다양한 화면 크기에서 더 나은 레이아웃 유연성을 제공합니다.

src/app/(with-header)/myactivity/components/ReservationForm.tsx (1)

36-36: 레이아웃 일관성 개선을 승인합니다.

수평 패딩을 px-16에서 px-0으로 변경하여 활동 관련 폼 컴포넌트들 간의 일관성을 유지하는 것이 적절합니다.

src/components/Header.tsx (1)

78-78: 반응형 간격 조정을 승인합니다.

모바일에서 gap-8, 중간 화면 이상에서 gap-24로 설정하여 화면 크기에 따른 적절한 간격을 제공합니다.

src/hooks/useMyPageQueries.ts (1)

51-51: 전역 상태 동기화 개선을 승인합니다.

헤더의 사용자 정보와 프로필 페이지의 상태를 동기화하는 것은 좋은 개선사항입니다. 이를 통해 닉네임 변경이 헤더에 실시간으로 반영됩니다.

Also applies to: 65-65, 87-87

src/components/Footer.tsx (2)

8-8: 간격 조정이 적절합니다

모바일 버전에서 요소들 간의 간격을 12에서 25로 늘린 것은 가독성과 터치 인터페이스 사용성을 개선하는 좋은 변경입니다.


10-10: JSX에서 자체 종료 태그 형태를 사용하세요

저작권 텍스트 업데이트는 좋습니다만, JSX에서는 <br />보다는 <br /> 형태의 자체 종료 태그를 사용하는 것이 권장됩니다.

-<div className='text-center md:text-left'>@NoSleepNoBugs<br />2025</div>
+<div className='text-center md:text-left'>@NoSleepNoBugs<br />2025</div>

Likely an incorrect or invalid review comment.

src/app/(with-header)/components/CategoryFilter.tsx (2)

6-6: 필요한 React hooks 가져오기가 적절합니다

드래그 스크롤 기능 구현을 위해 필요한 hooks들을 적절히 가져왔습니다.


87-89: 그라데이션 오버레이 구현이 훌륭합니다

사용자가 처음 상호작용할 때까지만 그라데이션을 보여주는 것은 스크롤 가능성을 시각적으로 알려주는 좋은 UX 패턴입니다.

src/app/(with-header)/components/PopularCard.tsx (1)

34-34: 텍스트 잘림 처리가 적절히 적용되었습니다

line-clamp-2-custom 클래스를 추가하여 제목이 2줄을 초과할 때 적절히 잘리도록 처리한 것이 좋습니다. 카드 레이아웃의 일관성을 유지하는 데 도움이 됩니다.

src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx (1)

53-53: 패딩 조정이 적절합니다

최외곽 컨테이너의 수평 패딩을 제거한 것은 레이아웃 일관성을 위한 적절한 조정으로 보입니다. 내부 컨테이너에서 여전히 적절한 패딩을 유지하고 있어 콘텐츠 가독성에는 문제가 없습니다.

src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts (1)

159-160: 쿼리 캐시 무효화 로직이 올바르게 구현되었습니다.

체험 수정 후 관련 리스트들이 실시간으로 업데이트되도록 하는 캐시 무효화가 적절히 추가되었습니다. 생성 폼과 일관된 패턴을 따르고 있어 좋습니다.

src/components/ProfileDropdown.tsx (1)

63-69: UI 스타일링 개선이 적절합니다.

중앙 정렬과 둥근 모서리 추가로 일관된 UI 디자인을 구현했습니다.

src/app/(with-header)/components/ExperienceCard.tsx (3)

19-19: 호버 효과가 잘 구현되었습니다.

카드에 부드러운 상승 애니메이션을 추가하여 사용자 인터랙션을 개선했습니다. PR 목표에 부합하는 모션 효과입니다.


25-25: 이미지 호버 효과가 적절합니다.

이미지에 확대 효과를 추가하여 시각적 피드백을 향상시켰습니다.


35-35: 텍스트 스타일링 개선이 좋습니다.

커스텀 line-clamp 클래스 사용으로 일관된 텍스트 줄임 처리를 구현했습니다. margin-bottom 사용도 적절합니다.

src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts (1)

60-61: 쿼리 캐시 무효화가 올바르게 구현되었습니다.

체험 생성 후 관련 리스트들이 자동으로 업데이트되도록 하는 캐시 무효화 로직이 적절히 추가되었습니다. 수정 폼과 일관된 패턴을 따르고 있습니다.

src/components/Dropdown.tsx (3)

37-39: 컴포넌트 커스터마이징 옵션이 잘 추가되었습니다.

드롭다운의 각 부분에 대한 스타일링 옵션을 제공하여 컴포넌트의 재사용성과 유연성을 크게 향상시켰습니다.


137-137: 새로운 className props 적용이 적절합니다.

각 요소에 커스텀 클래스명을 올바르게 적용하여 외부에서 스타일을 세밀하게 제어할 수 있도록 했습니다.

Also applies to: 173-173, 193-193


198-206: 체크 아이콘 주석 처리가 합리적입니다.

향후 사용을 위해 코드를 보존하면서 현재는 숨기는 방식이 적절합니다. 필요시 쉽게 복원할 수 있습니다.

src/app/(with-header)/myactivity/components/ScheduleSelect.tsx (1)

33-60: 반응형 레이아웃 개선 승인

flex-wrap과 flex-1, min-w-0를 사용한 반응형 레이아웃 개선이 잘 적용되었습니다. 작은 화면에서도 입력 필드들이 적절히 배치될 것으로 보입니다.

src/app/api/experiences/getPopularExperiences.ts (1)

14-27: 커서 기반 페이지네이션 구현 승인

무한 스크롤을 위한 커서 기반 페이지네이션이 잘 구현되었습니다. cursorId 파라미터의 옵셔널 처리도 적절합니다.

src/app/(with-header)/components/PopularExperiences.tsx (1)

20-39: 무한 스크롤 구현 승인

React Query의 useInfiniteQuery를 활용한 무한 스크롤 구현이 매우 잘 되었습니다. 타입 정의와 페이지네이션 로직이 명확합니다.

src/app/(with-header)/components/ExperienceList.tsx (2)

47-47: 모바일 가독성을 위한 패딩 조정 👍

px-16 md:px-24 lg:px-0 변경으로 작은 화면에서 좌우 여백이 좁아져 카드가 더 넉넉하게 보입니다. 디자인 의도와 일치하며 부작용도 없어 보입니다.


91-91: 브레이크포인트별 padding 값이 비선형입니다

pb-30 → md:pb-150 → lg:pb-100 으로 중간 구간이 가장 크고, 더 큰 화면에서 다시 줄어듭니다. 디자인 시스템 의도라면 문제없지만, 오타일 가능성도 있으니 한번 더 확인 바랍니다.

Comment on lines +17 to +49
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
>
<BannerSection keyword={keyword} />
</motion.div>

{isSearchMode ? (
<ExperienceList keyword={keyword} isSearchMode />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<ExperienceList keyword={keyword} isSearchMode />
</motion.div>
) : (
<>
<PopularExperiences />
<ExperienceList />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<PopularExperiences />
</motion.div>

<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<ExperienceList />
</motion.div>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

애니메이션 구현 승인 및 접근성 개선 제안

framer-motion을 사용한 스태거드 애니메이션이 잘 구현되었습니다. 사용자 경험을 향상시키는 좋은 개선사항입니다.

다만, 접근성을 위해 prefers-reduced-motion 미디어 쿼리를 고려하는 것이 좋겠습니다.

+  const shouldReduceMotion = typeof window !== 'undefined' && 
+    window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+
+  const animationProps = shouldReduceMotion ? {} : {
+    initial: { opacity: 0, y: 20 },
+    animate: { opacity: 1, y: 0 },
+    transition: { duration: 0.6, ease: 'easeOut' }
+  };

   return (
     <main>
       <motion.div
-        initial={{ opacity: 0, y: 20 }}
-        animate={{ opacity: 1, y: 0 }}
-        transition={{ duration: 0.6, ease: 'easeOut' }}
+        {...animationProps}
       >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
>
<BannerSection keyword={keyword} />
</motion.div>
{isSearchMode ? (
<ExperienceList keyword={keyword} isSearchMode />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<ExperienceList keyword={keyword} isSearchMode />
</motion.div>
) : (
<>
<PopularExperiences />
<ExperienceList />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<PopularExperiences />
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<ExperienceList />
</motion.div>
// Inside your BasePage component, before the return:
const shouldReduceMotion =
typeof window !== 'undefined' &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const animationProps = shouldReduceMotion
? {}
: {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.6, ease: 'easeOut' },
};
return (
<main>
<motion.div {...animationProps}>
<BannerSection keyword={keyword} />
</motion.div>
{isSearchMode ? (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<ExperienceList keyword={keyword} isSearchMode />
</motion.div>
) : (
<>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<PopularExperiences />
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<ExperienceList />
</motion.div>
</>
)}
</main>
);
🤖 Prompt for AI Agents
In src/app/(with-header)/components/BasePage.tsx around lines 17 to 49, the
staggered animations using framer-motion are implemented well but lack
consideration for users who prefer reduced motion. To fix this, detect the
user's `prefers-reduced-motion` setting using a media query and conditionally
disable or simplify the animations accordingly. This can be done by checking the
media query in a React hook or utility and adjusting the motion props to avoid
motion when the preference is set.

Comment on lines +19 to +58
const scrollRef = useRef<HTMLDivElement | null>(null);
const [hasInteracted, setHasInteracted] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const startX = useRef(0);
const scrollLeft = useRef(0);

const handleFirstInteraction = () => {
if (!hasInteracted) setHasInteracted(true);
};

const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
setIsDragging(true);
startX.current = e.pageX - (scrollRef.current?.offsetLeft ?? 0);
scrollLeft.current = scrollRef.current?.scrollLeft ?? 0;
handleFirstInteraction();
};

const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!isDragging || !scrollRef.current) return;
e.preventDefault();
const x = e.pageX - scrollRef.current.offsetLeft;
const walk = (x - startX.current) * 1;
scrollRef.current.scrollLeft = scrollLeft.current - walk;
handleFirstInteraction();
};

const handleMouseUpOrLeave = () => {
setIsDragging(false);
};

const handleScroll = () => {
handleFirstInteraction();
};

useEffect(() => {
const el = scrollRef.current;
if (!el) return;
el.addEventListener('scroll', handleScroll);
return () => el.removeEventListener('scroll', handleScroll);
}, []);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

드래그 스크롤 구현이 잘 되었지만 접근성을 개선하세요

드래그 스크롤 기능이 잘 구현되었고, 사용자 상호작용에 따른 그라데이션 오버레이 제어도 훌륭한 UX입니다. 하지만 접근성 측면에서 개선이 필요합니다.

정적 분석 도구의 경고를 해결하기 위해 다음과 같이 개선하세요:

      <div
        ref={scrollRef}
        className={cn(
          'relative z-20 flex w-full gap-8 pr-15 overflow-x-auto whitespace-nowrap no-scrollbar cursor-grab active:cursor-grabbing select-none',
          className,
        )}
+       role="tablist"
+       tabIndex={0}
+       aria-label="카테고리 필터"
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUpOrLeave}
        onMouseLeave={handleMouseUpOrLeave}
+       onKeyDown={(e) => {
+         if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
+           e.preventDefault();
+           const scrollAmount = 100;
+           if (scrollRef.current) {
+             scrollRef.current.scrollLeft += e.key === 'ArrowRight' ? scrollAmount : -scrollAmount;
+             handleFirstInteraction();
+           }
+         }
+       }}
      >

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/(with-header)/components/CategoryFilter.tsx around lines 19 to 58,
the drag scroll implementation lacks accessibility improvements and triggers
static analysis warnings. To fix this, add keyboard event handlers to support
keyboard navigation for scrolling, ensure the scroll container has appropriate
ARIA roles and tabIndex for focusability, and clean up event listeners properly
in useEffect. This will enhance accessibility and resolve static analysis tool
warnings.

Comment on lines +51 to 56
<p className="text-left pt-24 lg:pt-40 text-black text-2xl md:text-3xl">
<span className="text-primary font-bold">{keyword}</span>(으)로 검색한 결과입니다.
</p>
<p className="text-left text-sm font-normal ml-4 md:ml-0 mt-8 mb-16">
<p className="text-left text-lg font-normal mt-8 mb-16">
<span className="font-semibold">{totalCount}</span>개의 결과
</p>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

검색 안내 문구에 접근성 힌트 추가 권장

검색 결과 변화가 동적으로 발생하므로, 스크린리더 사용자를 위해 role="status" 혹은 aria-live="polite" 속성을 부여하면 접근성이 향상됩니다.

-<p className="text-left pt-24 lg:pt-40 text-black text-2xl md:text-3xl">
+<p
+  className="text-left pt-24 lg:pt-40 text-black text-2xl md:text-3xl"
+  role="status"
+  aria-live="polite"
+>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<p className="text-left pt-24 lg:pt-40 text-black text-2xl md:text-3xl">
<span className="text-primary font-bold">{keyword}</span>() 검색한 결과입니다.
</p>
<p className="text-left text-sm font-normal ml-4 md:ml-0 mt-8 mb-16">
<p className="text-left text-lg font-normal mt-8 mb-16">
<span className="font-semibold">{totalCount}</span>
</p>
<p
className="text-left pt-24 lg:pt-40 text-black text-2xl md:text-3xl"
role="status"
aria-live="polite"
>
<span className="text-primary font-bold">{keyword}</span>() 검색한 결과입니다.
</p>
<p className="text-left text-lg font-normal mt-8 mb-16">
<span className="font-semibold">{totalCount}</span>
</p>
🤖 Prompt for AI Agents
In src/app/(with-header)/components/ExperienceList.tsx around lines 51 to 56,
the search result text updates dynamically but lacks accessibility hints for
screen readers. Add either role="status" or aria-live="polite" attribute to the
container element wrapping the dynamic search result text to notify assistive
technologies of content changes, improving accessibility.

Comment on lines +64 to 88
<div className='relative flex justify-between items-center mb-40 pr-120'>
<CategoryFilter
selectedCategory={selectedCategory}
onChange={(category) => {
setSelectedCategory(category);
setCurrentPage(1);
}}
/>
<Dropdown
className='w-200'
placeholder='가격'
options={SORT_OPTIONS}
value={sortOption && SORT_LABEL_MAP[sortOption as keyof typeof SORT_LABEL_MAP] || ''}
onChange={(label: keyof typeof SORT_VALUE_MAP) => {
const value = SORT_VALUE_MAP[label];
setSortOption(value);
setCurrentPage(1);
}}
/>
{/* <div className=''> */}
<Dropdown
className='absolute right-0 md:w-130'
buttonClassName='flex flex-row items-center justify-between gap-0 border-nomad border rounded-[15px] text-md py-9 px-15'
listboxClassName='px-0 py-0'
optionClassName='pl-10 pr-0 py-9 text-md'
placeholder='가격'
options={SORT_OPTIONS}
value={sortOption && SORT_LABEL_MAP[sortOption as keyof typeof SORT_LABEL_MAP] || ''}
onChange={(label: keyof typeof SORT_VALUE_MAP) => {
const value = SORT_VALUE_MAP[label];
setSortOption(value);
setCurrentPage(1);
}}
/>
{/* </div> */}
</div>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

절대 위치 Dropdown이 다른 요소를 가릴 가능성

relative + absolute 조합에 pr-120 / md:w-130 과 같은 커스텀 spacing-token을 사용하면
① Tailwind 설정 누락 시 빌드 오류 발생
② 작은 화면에서 카테고리 필터 영역을 덮어 클릭이 안 될 수 있습니다.

플렉스 레이아웃을 그대로 활용하면 여유 padding 없이도 동일한 정렬이 가능합니다.

-<div className='relative flex justify-between items-center mb-40 pr-120'>
+<div className='flex justify-between items-center mb-40'>-  <Dropdown
-    className='absolute right-0 md:w-130'
+  <Dropdown
+    className='ml-auto w-[130px]'

덧붙여, Lines 72 / 87의 주석 처리된 div 는 불필요하니 정리해주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className='relative flex justify-between items-center mb-40 pr-120'>
<CategoryFilter
selectedCategory={selectedCategory}
onChange={(category) => {
setSelectedCategory(category);
setCurrentPage(1);
}}
/>
<Dropdown
className='w-200'
placeholder='가격'
options={SORT_OPTIONS}
value={sortOption && SORT_LABEL_MAP[sortOption as keyof typeof SORT_LABEL_MAP] || ''}
onChange={(label: keyof typeof SORT_VALUE_MAP) => {
const value = SORT_VALUE_MAP[label];
setSortOption(value);
setCurrentPage(1);
}}
/>
{/* <div className=''> */}
<Dropdown
className='absolute right-0 md:w-130'
buttonClassName='flex flex-row items-center justify-between gap-0 border-nomad border rounded-[15px] text-md py-9 px-15'
listboxClassName='px-0 py-0'
optionClassName='pl-10 pr-0 py-9 text-md'
placeholder='가격'
options={SORT_OPTIONS}
value={sortOption && SORT_LABEL_MAP[sortOption as keyof typeof SORT_LABEL_MAP] || ''}
onChange={(label: keyof typeof SORT_VALUE_MAP) => {
const value = SORT_VALUE_MAP[label];
setSortOption(value);
setCurrentPage(1);
}}
/>
{/* </div> */}
</div>
<div className='flex justify-between items-center mb-40'>
<CategoryFilter
selectedCategory={selectedCategory}
onChange={(category) => {
setSelectedCategory(category);
setCurrentPage(1);
}}
/>
{/* <div className=''> */}
<Dropdown
className='ml-auto w-[130px]'
buttonClassName='flex flex-row items-center justify-between gap-0 border-nomad border rounded-[15px] text-md py-9 px-15'
listboxClassName='px-0 py-0'
optionClassName='pl-10 pr-0 py-9 text-md'
placeholder='가격'
options={SORT_OPTIONS}
value={sortOption && SORT_LABEL_MAP[sortOption as keyof typeof SORT_LABEL_MAP] || ''}
onChange={(label: keyof typeof SORT_VALUE_MAP) => {
const value = SORT_VALUE_MAP[label];
setSortOption(value);
setCurrentPage(1);
}}
/>
{/* </div> */}
</div>
🤖 Prompt for AI Agents
In src/app/(with-header)/components/ExperienceList.tsx between lines 64 and 88,
the Dropdown component uses absolute positioning with custom padding and width
tokens that may cause build errors if Tailwind config is missing and can overlap
the CategoryFilter on small screens. Remove the absolute positioning and custom
padding/width classes, instead rely on the existing flex layout to align
elements properly without extra spacing. Also, delete the commented-out div tags
on lines 72 and 87 as they are unnecessary.

Comment on lines +32 to +33
queryFn: ({ pageParam = undefined }) =>
getPopularExperiences(pageParam as number | undefined), // 타입 단언 필요
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

타입 단언 대신 타입 가드 사용 고려

타입 단언보다는 타입 가드나 더 안전한 타입 처리를 고려해보세요.

-    queryFn: ({ pageParam = undefined }) =>
-      getPopularExperiences(pageParam as number | undefined), // 타입 단언 필요
+    queryFn: ({ pageParam }) =>
+      getPopularExperiences(typeof pageParam === 'number' ? pageParam : undefined),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
queryFn: ({ pageParam = undefined }) =>
getPopularExperiences(pageParam as number | undefined), // 타입 단언 필요
queryFn: ({ pageParam }) =>
getPopularExperiences(typeof pageParam === 'number' ? pageParam : undefined),
🤖 Prompt for AI Agents
In src/app/(with-header)/components/PopularExperiences.tsx around lines 32 to
33, replace the type assertion (pageParam as number | undefined) with a type
guard to safely check the type of pageParam before passing it to
getPopularExperiences. Implement a conditional check to verify if pageParam is a
number or undefined, and handle other cases appropriately to ensure type safety
without using type assertions.

Comment on lines +4 to 8
export interface ExperienceResponse {
cursorId: number;
totalCount: number;
activities: Experience[];
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

인터페이스 이름 충돌 가능성 확인

ExperienceResponse라는 이름이 다른 파일의 유사한 인터페이스와 충돌할 수 있습니다. 더 구체적인 이름을 사용하는 것이 좋을 수 있습니다.


🏁 Script executed:

#!/bin/bash
# ExperienceResponse 인터페이스 중복 확인
ast-grep --pattern 'interface ExperienceResponse {
  $$$
}'

Length of output: 1044


ExperienceResponse 인터페이스 중복 정의 확인 및 에러 처리 보완 권장

중복 정의 위치:

  • src/types/experienceListTypes.ts: export interface ExperienceResponse
  • src/app/api/experiences/getPopularExperiences.ts: export interface ExperienceResponse
  • src/app/api/experiences/getExperiences.ts: interface ExperienceResponse

제안 사항:

  • 전역 타입 파일(src/types/experienceListTypes.ts)에 정의된 ExperienceResponse를 재사용하거나,
    API별로 구체적인 이름(PopularExperiencesResponse, ExperiencesResponse)으로 분리하세요.
  • fetch 호출 시 네트워크 오류나 비정상 응답에 대비해 try/catch를 추가하고,
    에러 발생 시 적절히 처리(예: 예외 던지기, 사용자 알림)하는 로직을 구현해 주세요.
🤖 Prompt for AI Agents
In src/app/api/experiences/getPopularExperiences.ts around lines 4 to 8, the
ExperienceResponse interface is redundantly defined, causing potential
conflicts. Refactor by removing this local definition and importing the
ExperienceResponse interface from the global type file
src/types/experienceListTypes.ts, or rename it to a more specific name like
PopularExperiencesResponse if API-specific differentiation is needed.
Additionally, wrap the fetch call in a try/catch block to handle network errors
or unexpected responses, and implement proper error handling such as throwing
exceptions or notifying the user.

Comment on lines +147 to +171
@layer utilities {
.line-clamp-1-custom {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}

.line-clamp-2-custom {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}

.line-clamp-3-custom {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

브라우저 호환성을 확인하세요

새로운 line-clamp 유틸리티 클래스들이 올바르게 구현되었습니다. -webkit-line-clamp 속성은 현재 대부분의 현대 브라우저에서 지원되지만, 지원되지 않는 브라우저에서의 폴백 처리를 고려해보세요.

다음 스크립트를 통해 최신 브라우저 지원 상황을 확인해보세요:


🌐 Web query:

-webkit-line-clamp CSS property browser support 2025

💡 Result:

As of August 2025, the -webkit-line-clamp CSS property remains a non-standard, vendor-prefixed feature primarily supported in WebKit-based browsers like Safari and Chrome. This property allows developers to limit the number of lines of text displayed in a block element, truncating excess content with an ellipsis.

Current Browser Support:

  • Safari: Fully supports -webkit-line-clamp without the need for additional prefixes.
  • Chrome: Supports -webkit-line-clamp with the -webkit- prefix.
  • Firefox: Does not support -webkit-line-clamp.
  • Edge: As a Chromium-based browser, Edge supports -webkit-line-clamp with the -webkit- prefix.

Standardization Efforts:

The CSS Working Group has been working on standardizing the line-clamp feature. In June 2025, they resolved to make -webkit-line-clamp a shorthand for the standard line-clamp property, provided no compatibility issues arise. (w3.org) This move aims to align the vendor-prefixed version with the standardized property, facilitating broader adoption and interoperability.

Recommendations:

Given the ongoing standardization efforts and the current lack of support in Firefox, it's advisable to use the standard line-clamp property in conjunction with the -webkit-line-clamp for broader compatibility. This approach ensures that your styles are applied correctly across different browsers, including those that support the unprefixed version.

Example Usage:

.my-text-block {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  text-overflow: ellipsis;
}

.my-text-block {
  display: block;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  text-overflow: ellipsis;
}

In this example, the -webkit-line-clamp property is used to limit the text to three lines, with excess content truncated and replaced by an ellipsis. The standard line-clamp property is included to ensure future compatibility as browser support evolves.

By adopting this approach, you can create more robust and future-proof web designs that gracefully handle text truncation across various browsers.

Citations:


브라우저 호환성 및 표준 속성 추가 제안

src/app/globals.css (147–171)에 정의된 .line-clamp-{1,2,3}-custom 유틸리티는 기능적으로 올바르지만,
-webkit-line-clamp는 WebKit 기반 브라우저(Safari, Chrome, Edge)에서만 동작하며 Firefox에서는 무시됩니다.
향후 표준화된 line-clamp 속성을 함께 사용하거나, @supports로 대체 구현을 제공하는 것을 권장드립니다.

수정 예시:

 @layer utilities {
   .line-clamp-1-custom {
     display: -webkit-box;
     -webkit-box-orient: vertical;
-    -webkit-line-clamp: 1;
+    /* WebKit 전용 */
+    -webkit-line-clamp: 1;
+    /* 표준 속성 (미래 호환성 확보) */
+    line-clamp: 1;
     overflow: hidden;
     text-overflow: ellipsis;
   }
   /* .line-clamp-2-custom, .line-clamp-3-custom에도 동일하게 적용 */
 }
  • Firefox 지원이 필요하다면 JS 폴리필 또는 max-height/line-height 기반 페일백을 고려하세요.
  • @supports(line-clamp: 1) 조건부 스타일을 추가해 비표준 속성 우선순위를 관리할 수 있습니다.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@layer utilities {
.line-clamp-1-custom {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.line-clamp-2-custom {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.line-clamp-3-custom {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}
@layer utilities {
.line-clamp-1-custom {
display: -webkit-box;
-webkit-box-orient: vertical;
/* WebKit 전용 */
-webkit-line-clamp: 1;
/* 표준 속성 (미래 호환성 확보) */
line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.line-clamp-2-custom {
display: -webkit-box;
-webkit-box-orient: vertical;
/* WebKit 전용 */
-webkit-line-clamp: 2;
/* 표준 속성 (미래 호환성 확보) */
line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.line-clamp-3-custom {
display: -webkit-box;
-webkit-box-orient: vertical;
/* WebKit 전용 */
-webkit-line-clamp: 3;
/* 표준 속성 (미래 호환성 확보) */
line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
}
🤖 Prompt for AI Agents
In src/app/globals.css between lines 147 and 171, the .line-clamp-{1,2,3}-custom
classes use the non-standard -webkit-line-clamp property which only works in
WebKit browsers. To improve cross-browser compatibility, add the standard
line-clamp property alongside -webkit-line-clamp and use @supports queries to
provide fallback styles or alternative implementations for browsers like
Firefox. Consider adding max-height and line-height based fallbacks or a JS
polyfill if Firefox support is required.

{isOpen && (
<NotificationDropdown
className='md:right- fixed inset-0 md:absolute md:top-90 md:left-800'
className='fixed inset-0 md:absolute md:top-90 md:left-auto md:right-20 lg:right-10 z-50'
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

복잡한 포지셔닝 클래스를 단순화하거나 문서화하세요.

NotificationDropdown의 포지셔닝 클래스가 매우 복잡합니다. fixed inset-0md:absolute 등 서로 다른 포지셔닝 전략이 혼재되어 있어 유지보수가 어려울 수 있습니다.

다음과 같은 개선을 고려해보세요:

  1. 포지셔닝 로직을 별도 컴포넌트나 훅으로 분리
  2. 또는 최소한 주석으로 각 브레이크포인트별 의도를 설명
              {isOpen && (
+               {/* 모바일: 전체화면 오버레이, 데스크톱: 우상단 절대위치 */}
                <NotificationDropdown
                  className='fixed inset-0 md:absolute md:top-90 md:left-auto md:right-20 lg:right-10 z-50'
                  onClose={() => setIsOpen(false)}
                />
              )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className='fixed inset-0 md:absolute md:top-90 md:left-auto md:right-20 lg:right-10 z-50'
{isOpen && (
{/* 모바일: 전체화면 오버레이, 데스크톱: 우상단 절대위치 */}
<NotificationDropdown
className='fixed inset-0 md:absolute md:top-90 md:left-auto md:right-20 lg:right-10 z-50'
onClose={() => setIsOpen(false)}
/>
)}
🤖 Prompt for AI Agents
In src/components/Header.tsx at line 97, the NotificationDropdown's positioning
classes combine conflicting strategies like 'fixed inset-0' and 'md:absolute',
making maintenance difficult. Simplify the positioning by either extracting the
logic into a separate component or custom hook that manages responsive
positioning, or add clear comments explaining the intent of each breakpoint's
positioning to improve readability and maintainability.

Comment on lines +58 to +63
className='absolute top-50 right-0 z-50 mt-12 w-120 rounded-md border border-gray-200 bg-white shadow-md'
onClick={(e) => e.stopPropagation()}
>
<Link
href='/mypage'
className='block w-full px-16 py-12 text-left hover:bg-gray-50'
className='block w-full px-16 py-12 hover:bg-gray-50 rounded-[15px] text-center'
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스타일링 개선은 좋지만 접근성 문제를 해결해야 합니다.

UI 스타일링 변경사항은 적절하지만, 정적 분석 도구가 지적한 접근성 문제를 해결해야 합니다.

다음 수정사항을 적용하여 키보드 네비게이션 지원을 추가하세요:

        <div
-          className='absolute top-50 right-0 z-50 mt-12 w-120 rounded-md border border-gray-200 bg-white shadow-md'
-          onClick={(e) => e.stopPropagation()}
+          className='absolute top-50 right-0 z-50 mt-12 w-120 rounded-md border border-gray-200 bg-white shadow-md'
+          role="menu"
+          onKeyDown={(e) => {
+            if (e.key === 'Escape') {
+              setIsOpen(false);
+            }
+          }}
+          onClick={(e) => e.stopPropagation()}
        >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className='absolute top-50 right-0 z-50 mt-12 w-120 rounded-md border border-gray-200 bg-white shadow-md'
onClick={(e) => e.stopPropagation()}
>
<Link
href='/mypage'
className='block w-full px-16 py-12 text-left hover:bg-gray-50'
className='block w-full px-16 py-12 hover:bg-gray-50 rounded-[15px] text-center'
<div
className='absolute top-50 right-0 z-50 mt-12 w-120 rounded-md border border-gray-200 bg-white shadow-md'
role="menu"
onKeyDown={(e) => {
if (e.key === 'Escape') {
setIsOpen(false);
}
}}
onClick={(e) => e.stopPropagation()}
>
<Link
href='/mypage'
className='block w-full px-16 py-12 hover:bg-gray-50 rounded-[15px] text-center'
🧰 Tools
🪛 Biome (2.1.2)

[error] 58-63: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 58-63: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🤖 Prompt for AI Agents
In src/components/ProfileDropdown.tsx around lines 58 to 63, the dropdown menu
lacks keyboard accessibility support. To fix this, add appropriate keyboard
event handlers such as onKeyDown to handle keyboard navigation (e.g., closing
the menu on Escape key), ensure focus management by setting tabIndex where
needed, and use semantic HTML elements or ARIA roles to improve accessibility.
This will enable users to navigate the dropdown using the keyboard effectively.

Comment on lines +25 to 28
buttonClassName?: string;
listboxClassName?: string;
optionClassName?: string;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

JSDoc 문서를 업데이트하세요.

새로 추가된 스타일링 속성들(buttonClassName, listboxClassName, optionClassName)에 대한 설명을 JSDoc 주석에 추가하는 것이 좋겠습니다.

다음과 같이 문서를 보완할 수 있습니다:

 * @property className - 컴포넌트에 적용할 CSS 클래스 (크기, 위치 등)
 * @property disabled - 비활성화 여부
 * @property disableScroll - 스크롤 비활성화 여부
+ * @property buttonClassName - 드롭다운 버튼에 적용할 CSS 클래스
+ * @property listboxClassName - 드롭다운 목록 컨테이너에 적용할 CSS 클래스  
+ * @property optionClassName - 각 옵션 요소에 적용할 CSS 클래스
 */
🤖 Prompt for AI Agents
In src/types/dropdownTypes.ts around lines 25 to 28, the new styling properties
buttonClassName, listboxClassName, and optionClassName lack JSDoc comments.
Update the existing JSDoc block for this interface or type to include clear
descriptions for each of these properties, explaining their purpose and usage as
CSS class names for styling the button, listbox, and options respectively.

Copy link

@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: 3

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e9c62da and f926162.

📒 Files selected for processing (1)
  • src/components/Dropdown.tsx (5 hunks)
🔇 Additional comments (3)
src/components/Dropdown.tsx (3)

37-39: 스타일링 유연성을 위한 새로운 className props 추가가 잘 구현되었습니다.

buttonClassName, listboxClassName, optionClassName props 추가로 외부에서 각 요소별로 세밀한 스타일링이 가능해져 컴포넌트의 재사용성이 크게 향상되었습니다.


137-137: buttonClassName이 올바르게 적용되었습니다.

기본 스타일과 외부에서 전달된 스타일을 적절히 병합하여 스타일 우선순위가 올바르게 처리되었습니다.


173-173: listboxClassName이 올바르게 적용되었습니다.

리스트박스 컨테이너에 대한 외부 스타일링이 적절히 적용되어 드롭다운 목록의 커스터마이징이 가능해졌습니다.

import ChevronIcon from '@assets/svg/chevron';
import CheckIcon from '@assets/svg/check';
import { DropdownProps } from '@/types/dropdownTypes';
// import CheckIcon from '@assets/svg/check';
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

import 문을 주석 처리하는 대신 제거하는 것을 고려해보세요.

사용하지 않는 import 문은 주석 처리보다는 완전히 제거하는 것이 일반적입니다. 향후 다시 사용할 계획이 있다면 현재 형태를 유지할 수 있지만, 코드 정리 차원에서는 제거가 권장됩니다.

🤖 Prompt for AI Agents
In src/components/Dropdown.tsx at line 9, the import statement for CheckIcon is
commented out. Instead of commenting it out, remove this unused import entirely
to keep the code clean and maintainable. If you plan to use it later, you can
re-add it when needed.

? 'bg-nomad rounded text-white'
: 'hover:bg-gray-100',
)}
optionClassName,)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

optionClassName 적용에 문법 오류가 있습니다.

cn() 함수 호출에서 닫는 괄호와 쉼표의 위치가 잘못되었습니다.

다음과 같이 수정하세요:

-                      optionClassName,)}
+                      optionClassName,
+                    )}
🤖 Prompt for AI Agents
In src/components/Dropdown.tsx at line 193, there is a syntax error in the usage
of the cn() function related to the placement of the closing parenthesis and
comma around optionClassName. Fix this by correctly positioning the closing
parenthesis after all arguments and ensuring commas separate the arguments
properly within the cn() call.

Comment on lines +198 to +206
{/* <div className='flex w-24 justify-start'>
{isSelected && (
<CheckIcon
size={20}
showBackground={false}
className='text-white'
/>
)}
</div>
</div> */}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

주석 처리된 체크 아이콘 코드를 제거하거나 더 명확한 주석을 추가하세요.

현재 체크 아이콘 관련 코드가 주석 처리되어 있는데, 향후 사용 계획이 있다면 더 명확한 주석을 추가하거나, 완전히 제거할 계획이라면 삭제하는 것이 좋습니다.

향후 사용 계획이 있다면 다음과 같이 명확한 주석을 추가하세요:

-                    {/* <div className='flex w-24 justify-start'>
+                    {/* TODO: 체크 아이콘 기능 추후 재구현 예정
+                    <div className='flex w-24 justify-start'>
                       {isSelected && (
                         <CheckIcon
                           size={20}
                           showBackground={false}
                           className='text-white'
                         />
                       )}
                     </div> */}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* <div className='flex w-24 justify-start'>
{isSelected && (
<CheckIcon
size={20}
showBackground={false}
className='text-white'
/>
)}
</div>
</div> */}
{/* TODO:
<div className='flex w-24 justify-start'>
{isSelected && (
<CheckIcon
size={20}
showBackground={false}
className='text-white'
/>
)}
</div> */}
🤖 Prompt for AI Agents
In src/components/Dropdown.tsx around lines 198 to 206, the commented-out
CheckIcon code is unclear. Either remove this unused commented code entirely if
it is no longer needed, or add a clear comment explaining why it is commented
out and under what conditions it might be re-enabled in the future.

Copy link
Contributor

@minimo-9 minimo-9 left a comment

Choose a reason for hiding this comment

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

수정하시느라 고생하셨습니다!

Copy link
Contributor

@LeeCh0129 LeeCh0129 left a comment

Choose a reason for hiding this comment

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

QA에서 나온 이슈들 처리하느라 고생하셨습니다! 드디어 내일이면 끝이네요.

Copy link
Contributor

@evaain706 evaain706 left a comment

Choose a reason for hiding this comment

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

수정 수고하셨습니다!

@BokyungCodes BokyungCodes merged commit eceab37 into develop Aug 4, 2025
1 of 2 checks passed
@BokyungCodes BokyungCodes deleted the fix/121 branch August 4, 2025 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 api 통신관련 🐞 bug 버그관련 💻 chore 사소한 수정(빌드,패키지매니저등) ✨ Feature 기능구현 🎨 style 마크업 및 스타일링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[fix] QA(1,2차 통합) 후 나온 메인페이지, 체험 등록/수정 페이지 이슈 해결

5 participants