Skip to content

[FIX] 3차 스프린트 QA-2#351

Merged
bykbyk0401 merged 19 commits intodevelopfrom
fix/#350/3sp-qa2
Feb 12, 2026
Merged

[FIX] 3차 스프린트 QA-2#351
bykbyk0401 merged 19 commits intodevelopfrom
fix/#350/3sp-qa2

Conversation

@maylh
Copy link
Collaborator

@maylh maylh commented Feb 10, 2026

🛰️ 관련 이슈

해결한 이슈 번호를 작성해주세요
close #350

🧑‍💻 작업 내용

작업한 내용을 간략히 작성해주세요

  • 미해결 이슈들 작업 완료

🗯️ PR 포인트

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

  • 인기 템플스테이 캐러셀 무한으로 바꿔야해서, 메인 배너 로직 가져다 쓰면서 해당 부분 useInfiniteCarousel 커스텀 훅으로 분리했습니다

🚀 알게된 점

기록하며 개발하기!

📖 참고 자료 (선택)

참고했던 문서들 공유하기!

📸 스크린샷 (선택)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 검색창 자동 포커스 및 입력 길이(16자) 제한
    • 무한 스크롤형 캐러셀·향상된 드래그/스와이프, 표시 인덱스 명확화(displayIndex)
    • 카드 이미지 우선 로딩 옵션(priority) 추가 및 키보드 접근성 강화(Enter/Space)
  • 버그 수정

    • 필터 버튼 라벨 문법 수정
  • UI 개선

    • 모달 오버레이 z-index 조정, 헤더·카드 여백·패딩 소폭 조정
    • 버튼 바 레이아웃 개선 및 비활성화 상태 지원
    • 스크롤바 숨김 처리 및 터치 동작 개선

@maylh maylh self-assigned this Feb 10, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Warning

Rate limit exceeded

@maylh has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 26 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

무한 캐러셀 훅을 도입해 배너·인기 캐러셀의 드래그·자동재생 로직을 훅으로 이전하고, PopularCard 네비게이션을 앵커→클릭으로 변경했으며, 검색창 자동포커스·ButtonBar 비활성화 prop 및 여러 스타일·네이밍 조정이 적용되었습니다.

Changes

Cohort / File(s) Summary
무한 캐러셀 훅
src/hooks/useInfiniteCarousel.ts
새 훅 추가: 클론 경계 슬라이드, 드래그/터치 핸들러, 자동재생, displayIndex/dragOffset/isSwiped 산출 및 이벤트 핸들러 반환.
배너·캐러셀 컴포넌트
src/components/banner/MainBanner.tsx, src/components/carousel/popularCarousel/PopularCarousel.tsx, src/components/carousel/popularCarousel/CarouselIndex.tsx, src/components/banner/mainBanner.css.ts
로컬 캐러셀 상태 제거 후 useInfiniteCarousel 사용으로 재구성; 라우터 네비게이션·클릭 로깅 통합, 인덱스/표시 로직 및 스타일(터치 동작) 변경.
PopularCard 및 스타일
src/components/card/popularCard/PopularCard.tsx, src/components/card/popularCard/popularCard.css.ts, src/components/card/popularCard/rankBtn.css.ts
앵커→div 클릭/키보드 접근성으로 전환, priority prop 추가, 이미지 래퍼·슬라이드 구조로 CSS/클래스명 재구성, rankBox 포지셔닝 수정.
검색 관련 변경
src/components/search/searchBar/SearchBar.tsx, src/components/search/searchBar/searchBar.css.ts, src/components/search/searchHeader/SearchHeader.tsx, src/app/search/page.tsx
SearchBar/SearchHeader에 inputAutoFocus 추가(기본 false), ref 기반 자동 포커스, 입력 길이 16자 제한 및 스타일 조정; 페이지에서 autofocus 전달.
ButtonBar·필터 변경
src/components/common/button/buttonBar/ButtonBar.tsx, src/components/common/button/buttonBar/buttonBar.css.ts, src/components/filter/filterBottomSheetModal/FilterModalContent.tsx, src/components/filter/filterBottomSheetModal/filterModalContent.css.ts
ButtonBar에 isDisabled prop 추가 및 wrapper 구조 도입, 필터 리셋 문구 수정(한국어), totalCount === 0 시 버튼 비활성화 전달, WebKit 스크롤바 숨김 스타일 추가.
스타일·글로벌·스토리북 소소 변경
src/app/homePage.css.ts, src/app/searchResult/searchResultPage.css.ts, src/components/card/templeStayCard/infoSection.css.ts, src/styles/global.css.ts, src/stories/CarouselIndex.stories.ts, src/stories/PopularCard.stories.ts
modalOverlay zIndex 상승 및 배경색 제거, searchResult paddingTop 소폭 증가, 타이틀 고정 높이 제거, 전역 웹킷 스크롤 숨김 및 html paddingRight 추가, Storybook 인자명 currentIndex→displayIndex 및 PopularCard story에서 link 제거.
훅·기타 변경
src/hooks/useFilter.ts
useFilter에 priceAtom 초기화와 filterListInstance.resetAllStates() 호출 추가(검색 전 상태 초기화).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MainBanner
    participant useInfiniteCarousel
    participant Logger
    participant Router

    User->>MainBanner: drag / swipe / click
    MainBanner->>useInfiniteCarousel: attach handlers / forward pointer events
    useInfiniteCarousel-->>MainBanner: slides, displayIndex, dragOffset, isSwiped, handlers
    MainBanner->>Logger: logClickEvent (if click && !isSwiped)
    MainBanner->>Router: navigate to detail (if click && !isSwiped)
    Router-->>User: route change
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • bykbyk0401
  • seong-hui

Poem

🐰 캐러셀 빙글빙글, 발끝은 가벼이,
스와이프에 맞춰 훅이 춤을 추네,
포커스는 와서 조용히 눈을 밝혀,
쉬어가는 버튼은 때맞춰 깨어나고,
코드 들판에 당근 한 움큼 웃음이 피네.

🚥 Pre-merge checks | ✅ 1 | ❌ 4
❌ Failed checks (4 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive PR 제목은 관련 이슈 #350의 제목과 일치하지만, QA 고정사항의 구체적인 내용을 명시하지 않아 변경사항을 정확히 파악하기 어렵습니다. PR 제목을 더 구체적으로 수정하여 주요 변경사항(예: 'Refactor carousel to infinite loop with useInfiniteCarousel hook')을 명확히 하거나, 이슈 제목 자체를 더 설명적으로 변경해주세요.
Description check ❓ Inconclusive PR 설명이 템플릿 구조를 따르고 있으나, 작업 내용이 '미해결 이슈들 작업 완료'로 매우 모호하며 구체적인 변경사항이 부족합니다. 작업 내용 섹션을 더 상세히 작성하여 각 변경사항(무한 캐러셀 구현, useInfiniteCarousel 훅 추출, UI 및 스타일 조정 등)을 명확히 기술해주세요.
Linked Issues check ❓ Inconclusive PR은 이슈 #350을 연결했으나, 해당 이슈에는 '3차 스프린트 미해결 이슈 처리'라는 모호한 작업 목록만 있어 PR이 구체적인 요구사항을 충족하는지 판단할 수 없습니다. 연결된 이슈 #350의 작업 목록을 더 상세히 작성하거나, PR에서 이슈의 구체적인 요구사항에 대한 변경사항 매핑을 명확히 표시해주세요.
Out of Scope Changes check ❓ Inconclusive 많은 파일 변경사항이 존재하지만, 이슈 #350의 작업 목록이 '미해결 이슈 처리'로 명시되지 않아 범위 판단이 어렵습니다. 다만, useInfiniteCarousel 훅 추가, 캐러셀 리팩토링, 스타일 조정 등은 일반적으로 범위 내 변경으로 보입니다. 이슈 #350의 구체적인 작업 범위를 명시하여 변경사항이 범위 내에 있는지 확인하거나, PR 설명에 각 변경사항이 어느 이슈의 어느 요구사항을 해결하는지 매핑해주세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/#350/3sp-qa2

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 10, 2026

🪷 Storybook 확인 🪷
🔗 https://677fa9af1269762b7858c29f-hceggstwfx.chromatic.com/

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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/hooks/useFilter.ts (2)

40-75: ⚠️ Potential issue | 🟡 Minor

useCallback 의존성 배열에 setPrice 누락 — 파이프라인 실패

파이프라인 로그에서 react-hooks/exhaustive-deps 경고가 발생하고 있습니다. setPrice를 의존성 배열에 추가해야 합니다. (useSetAtom이 반환하는 setter는 jotai에서 안정적인 참조를 보장하므로 불필요한 리렌더를 유발하지 않습니다.)

🔧 수정 제안
-    [router, addStorageValue],
+    [router, addStorageValue, setPrice],

78-82: ⚠️ Potential issue | 🟠 Major

handleResetFilter에서 priceAtom 초기화 누락 — handleSearch와 불일치

handleSearch(Line 42-43)에서는 filterListInstance.resetAllStates()setPrice({ minPrice: 0, maxPrice: 30 })를 함께 호출하여 필터와 가격을 모두 초기화하지만, handleResetFilter에서는 filterListInstance.resetAllStates()만 호출합니다. 필터 초기화 버튼을 눌렀을 때 가격 슬라이더가 리셋되지 않는 버그가 발생할 수 있습니다.

🔧 수정 제안
 const handleResetFilter = async () => {
   filterListInstance.resetAllStates();
+  setPrice({ minPrice: 0, maxPrice: 30 });

   queryClient.invalidateQueries({ queryKey: ['filteredList'], exact: false });
 };
src/app/homePage.css.ts (1)

31-41: ⚠️ Potential issue | 🟡 Minor

modalOverlaybackgroundColor 제거 확인 완료 - 일관성 개선 필요

backgroundColor는 의도적으로 제거되었으며, 배경색은 Modal 컴포넌트의 modalBackdrop에서 background: theme.COLORS.black60으로 제공됩니다. 그러나 src/app/myPage/myPage.css.tsmodalOverlay는 여전히 backgroundColor: theme.COLORS.black60을 포함하고 있어 두 파일 간 구현이 불일치합니다. 일관성을 위해 myPage.css.ts의 정의를 정렬하거나, 둘 다 동일한 패턴을 따르도록 통일하는 것을 권장합니다.

🤖 Fix all issues with AI agents
In `@src/components/banner/MainBanner.tsx`:
- Line 47: The map callback in MainBanner.tsx declares an unused parameter
"index" causing an ESLint error; remove the unused "index" parameter from the
slides.map callback (change slides.map((banner, index) => ...) to
slides.map(banner => ...)) so the component uses only banner and continues to
use banner.uniqueKey as the key.
- Around line 7-9: 현재 import 정렬이 ESLint `import/order` 규칙을 위반하고 있습니다: 이동해야 할 심볼은
useInfiniteCarousel이며 next/image보다 위에 있어야 하고 import 그룹 사이에 빈 줄이 필요합니다;
MainBanner.tsx에서 import 구문을 재정렬하여 `useInfiniteCarousel`을 `next/image`보다 먼저 불러오고
서로 다른 그룹(예: 외부 라이브러리, 절대/앱 레벨 모듈, 로컬 파일) 사이에 빈 줄을 추가해 BANNER_DATA와 styles import
그룹과 분리되도록 수정하세요.

In `@src/components/card/popularCard/PopularCard.tsx`:
- Line 18: Remove the unused "link" prop from the PopularCardProps interface and
from the PopularCard component's parameter destructuring (remove any reference
to link in PopularCard), and also stop passing "link" from the parent
PopularCarousel where it currently forwards that prop; since navigation is
handled via PopularCarousel's onClick, ensure no "link" remains in the props
interface, component signature, or the parent prop spread/prop list to eliminate
the ESLint no-unused-vars error.
- Line 48: The onKeyDown handler currently uses short-circuiting so onClick()
never runs for Enter; replace it with a proper conditional that calls onClick()
when e.key is 'Enter' or ' ' and call e.preventDefault() to stop page scrolling
for Space; update the onKeyDown prop (referencing onKeyDown, e.key and onClick)
to check both keys explicitly, call e.preventDefault() for the matched keys,
then invoke onClick().

In `@src/components/carousel/popularCarousel/PopularCarousel.tsx`:
- Around line 84-92: In the onClick handler for the PopularCarousel (the arrow
handling isSwiped, router.push and logClickEvent), reverse the call order so
logClickEvent('click_popularity_card', { label: temple.id }) runs before
initiating navigation with router.push(`/detail/${temple.id}`), or alternatively
make logging use a sendBeacon-style delivery (e.g., ensure logClickEvent uses
navigator.sendBeacon or a synchronous fallback) so the event is reliably sent
before the page unmounts; update the onClick that references isSwiped,
router.push and logClickEvent accordingly.

In `@src/components/common/button/buttonBar/ButtonBar.tsx`:
- Line 1: Move the local CSS import for buttonBar.css below the component
imports and separate import groups with a blank line to satisfy ESLint ordering:
ensure imports for external packages and components (e.g., the TextBtn import
from `@components/common/button/textBtn/TextBtn`) appear first, then add an empty
line, then import { buttonBarWrapper, buttonBarContainer } from
'./buttonBar.css' in ButtonBar.tsx.

In `@src/components/search/searchBar/SearchBar.tsx`:
- Around line 20-24: useEffect currently uses an empty dependency array which
triggers a react-hooks/exhaustive-deps warning for the autoFocus prop; update
the effect in SearchBar.tsx that calls inputRef.current.focus() so its
dependency array includes autoFocus (i.e. change [] to [autoFocus]) to satisfy
the linter while preserving behavior—do not add inputRef to deps since refs are
stable, and ensure the effect still guards with if (autoFocus &&
inputRef.current) before calling focus().

In `@src/components/search/searchHeader/SearchHeader.tsx`:
- Line 20: The pipeline fails due to jsx-a11y/no-autofocus being triggered on
the SearchBar's autoFocus prop; update the prop name to avoid the lint rule and
clarify intent: change the prop passed in SearchHeader from autoFocus to
shouldFocusOnMount and update the SearchBar component (and any other callers) to
accept and use shouldFocusOnMount (the SearchBar's existing useEffect +
ref.focus() behavior stays the same), or alternatively add an inline
eslint-disable-next-line jsx-a11y/no-autofocus comment immediately above the JSX
if you prefer the temporary bypass.

In `@src/hooks/useInfiniteCarousel.ts`:
- Around line 113-119: The handlers object currently only returns mouse events,
so mobile touch swipes won't work; add equivalent touch handlers to the same
handlers object that call the existing functions: onTouchStart should call
handleDragStart using e.touches[0].clientX, onTouchMove should call
handleDragMove using e.touches[0].clientX, onTouchEnd should call handleDragEnd,
and onTouchCancel should also call handleDragEnd; keep onTransitionEnd as-is
(handleTransitionEnd) and ensure the event types are handled safely (guard
touches length) so handleDrag* functions receive a numeric clientX just like
mouse handlers.
- Around line 8-18: The hook useInfiniteCarousel currently builds cloned slides
from data[0] / data[data.length-1] and sets totalOriginalSlides = data.length
without guarding empty arrays; add a defensive check so when data.length === 0
you return/produce safe defaults (e.g., slides = [], totalOriginalSlides = 0 and
any derived values like displayIndex, currentIndex, or autoplay timers handle
zero-length gracefully) and only build clone-first/clone-last when data.length >
0 (refer to the slides variable and totalOriginalSlides in useInfiniteCarousel
and ensure any logic that computes displayIndex or uses totalOriginalSlides
avoids division/mod by zero or invalid indexing).
🧹 Nitpick comments (8)
src/hooks/useFilter.ts (1)

22-22: getCookie가 모듈 스코프에서 한 번만 평가됨

isLoggedIn이 모듈 최상위에서 평가되므로, 사용자가 로그인/로그아웃 후에도 값이 갱신되지 않습니다. 이 PR에서 도입된 변경은 아니지만, 훅 내부 또는 handleSearch 내부로 이동하는 것이 안전합니다.

♻️ 리팩터링 제안
-const isLoggedIn = getCookie('userNickname');
-
 const useFilter = () => {
   const { addStorageValue } = useLocalStorage();
   const setPrice = useSetAtom(priceAtom);
+  const isLoggedIn = getCookie('userNickname');
src/components/search/searchBar/searchBar.css.ts (1)

36-43: ::placeholdertransform 적용은 대부분의 브라우저에서 동작하지 않습니다.

CSS transform 속성은 ::placeholder 의사 요소에 적용되지 않습니다. input 요소 자체의 transform: scale(0.9375) (Line 34)이 placeholder 텍스트에도 함께 적용되므로, placeholder 내의 transformtransformOrigin은 불필요하며 제거해도 무방합니다.

수정 제안
   selectors: {
     '&::placeholder': {
       color: theme.COLORS.gray5,
       lineHeight: '1.5rem',
-      transform: 'scale(0.9375)',
-      transformOrigin: 'left center',
     },
   },
src/components/filter/filterBottomSheetModal/filterModalContent.css.ts (1)

16-21: Firefox에서 스크롤바가 여전히 표시될 수 있습니다.

::-webkit-scrollbar는 WebKit/Blink 기반 브라우저(Chrome, Safari)에서만 동작합니다. Firefox에서도 스크롤바를 숨기려면 scrollbarWidth: 'none'을 추가하는 것이 좋습니다.

🔧 Firefox 스크롤바 숨김 추가
 export const main = style({
   padding: '0 2rem',
   marginBottom: '7rem',
   overflowY: 'auto',
   maxHeight: 'calc(100vh - 280px)',
   height: '100vh',
+  scrollbarWidth: 'none',
 
   selectors: {
     '&::-webkit-scrollbar': {
       display: 'none',
     },
   },
 });
src/components/filter/filterBottomSheetModal/FilterModalContent.tsx (1)

29-48: useMemo에서 filtersState를 직접 사용하지 않아 ESLint 경고가 발생합니다.

파이프라인 경고: filtersStateuseMemo 콜백 내부에서 직접 참조되지 않고, filterListInstance.getGroupedSelectedFilters()를 대신 호출하고 있습니다. filtersState는 필터 변경 시 memo를 재계산하기 위한 트리거 역할인데, 이 패턴은 ESLint react-hooks/exhaustive-deps 규칙과 충돌합니다.

filtersState에서 직접 필터 값을 추출하거나, 불가능하다면 lint suppress 주석을 추가하는 것을 권장합니다.

🔧 옵션 A: filtersState에서 직접 필터 값 추출

filtersState에서 선택된 필터를 직접 계산하면 ESLint 경고를 해소하면서 filterListInstance 싱글톤 직접 접근도 줄일 수 있습니다.

🔧 옵션 B: lint suppress 주석 추가

현재 패턴을 유지하려면 의도를 명시하는 주석과 함께 suppress 처리합니다:

   // eslint-disable-next-line react-hooks/exhaustive-deps -- filtersState is used as a recomputation trigger
   }, [price, filtersState, searchText]);
src/styles/global.css.ts (1)

7-9: !important를 사용한 전역 html 패딩 오버라이드

모달 라이브러리(예: body-scroll-lock 등)가 스크롤바 보정을 위해 htmlpaddingRight를 동적으로 주입하는 것을 방지하는 패턴으로 보입니다. 의도한 동작이라면 괜찮지만, 왜 이 규칙이 필요한지 간단한 주석을 남겨두면 향후 유지보수에 도움이 됩니다.

💡 주석 추가 제안
+// 모달/오버레이 라이브러리가 스크롤바 보정으로 paddingRight를 주입하는 것을 방지
 globalStyle('html', {
   paddingRight: '0px !important',
 });
src/hooks/useInfiniteCarousel.ts (1)

55-94: 핸들러 함수 메모이제이션 고려

handleTransitionEnd, handleDragStart, handleDragMove, handleDragEnd가 매 렌더마다 새로 생성됩니다. 현재 사용 패턴에서는 큰 문제가 아니지만, 반환되는 handlers 객체가 매번 새로운 참조를 갖게 되므로, 소비 컴포넌트에서 React.memo나 의존성 배열에 handlers를 사용할 경우 불필요한 리렌더가 발생할 수 있습니다.

향후 필요 시 useCallbackuseMemo로 감싸는 것을 고려해 주세요.

src/components/carousel/popularCarousel/popularCarousel.css.ts (1)

5-46: popularCard.css.ts와 스타일이 거의 완전히 중복됩니다.

container, slideList, slideItem, imageWrapper, templestayName, slideContent — 이 6개 스타일이 src/components/card/popularCard/popularCard.css.ts에도 동일하게(또는 거의 동일하게) 정의되어 있습니다. PopularCard.tsx에서는 popularCard.css를 import하고, PopularCarousel.tsx에서는 popularCarousel.css를 import하는데, 실제로 카드 내부 레이아웃 스타일(slideItem, imageWrapper, slideContent, templestayName)은 카드 쪽에만 두고, 캐러셀 래퍼 스타일(container, slideList)만 이 파일에 유지하는 것이 DRY 원칙에 부합합니다.

현재 구조에서는 스타일 변경 시 두 파일을 동시에 수정해야 하므로 유지보수 부담이 커집니다.

src/components/carousel/popularCarousel/PopularCarousel.tsx (1)

63-99: 캐러셀 슬라이드 너비가 하드코딩되어 CSS와 결합되어 있습니다.

Line 68의 33.5 + 2popularCarousel.css.tsslideList width(33.5rem)와 gap(2rem)에 의존합니다. 이 값들이 CSS에서 변경되면 여기서도 수동으로 맞춰야 합니다. 당장 문제는 아니지만, 상수로 추출하여 한 곳에서 관리하면 유지보수가 편해집니다.

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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/search/searchBar/SearchBar.tsx (1)

62-66: ⚠️ Potential issue | 🟡 Minor

useEffect 의존성 배열에 pathname 누락.

pathname이 의존성 배열에 포함되지 않아 react-hooks/exhaustive-deps 경고가 발생할 수 있습니다. 마운트 시에만 실행하려는 의도라면 ESLint disable 주석을, 경로 변경에 반응하려면 의존성에 추가해야 합니다.

수정 제안 (마운트 시 1회만 실행 의도인 경우)
   useEffect(() => {
     if (pathname === '/search') {
       localStorage.setItem('prevPage', '/search');
     }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);
수정 제안 (경로 변경에도 반응해야 하는 경우)
   useEffect(() => {
     if (pathname === '/search') {
       localStorage.setItem('prevPage', '/search');
     }
-  }, []);
+  }, [pathname]);
🤖 Fix all issues with AI agents
In `@src/components/carousel/popularCarousel/PopularCarousel.tsx`:
- Around line 6-10: Reorder the import statements to satisfy ESLint import
ordering: move the modules from external/package scopes so that
'@tanstack/react-query' (useQueryClient), 'cookies-next' (getCookie) and
internal hook alias '@hooks/useInfiniteCarousel' (useInfiniteCarousel) appear
before the Next import 'next/navigation' (useRouter); update the top of
PopularCarousel.tsx to import useQueryClient, getCookie and useInfiniteCarousel
above useRouter so ESLint no longer flags the import order.

In `@src/hooks/useInfiniteCarousel.ts`:
- Around line 131-144: The ternary expression assigning handlers when hasSlides
is true is mis-indented according to the ESLint indent rule; reformat the object
literal so its properties align with the surrounding indentation (fix
indentation for the block that contains onMouseDown, onMouseMove, onMouseUp,
onMouseLeave, onTouchStart, onTouchMove, onTouchEnd, onTransitionEnd) in
useInfiniteCarousel (the handlers assignment using hasSlides, and the functions
handleDragStart, handleDragMove, handleDragEnd, handleTransitionEnd). Ensure the
object braces and each property line follow the project's 8-space indenting
convention so ESLint no longer flags lines 133–143.
- Around line 75-97: When handleDragMove detects a vertical scroll and cancels
dragging, it only sets isDragging to false leaving isAnimate as false (set in
handleDragStart) so the carousel remains non-animated and auto-slide doesn't
resume; update handleDragMove to, when cancelling due to vertical scroll (the
branch that sets setIsDragging(false)), also call setIsAnimate(true) and restart
the auto-play (e.g., call the existing startAutoSlide or equivalent function
and/or undo stopAutoSlide) so animation and auto-slide are restored after the
cancel.

In `@src/styles/global.css.ts`:
- Around line 7-9: The globalStyle call currently hiding '::-webkit-scrollbar'
applies site-wide and harms accessibility; change it so you remove or stop using
the global '::-webkit-scrollbar' rule and instead apply scrollbar hiding only to
specific container selectors (e.g., carousel or modal container symbols) by
calling globalStyle with those container class/ID selectors; if you need broader
support include the Firefox equivalent by adding 'scrollbar-width: none' for
those scoped selectors and avoid a blanket globalStyle('::-webkit-scrollbar',
...) rule.
- Around line 11-13: The globalStyle override that force-removes paddingRight
via 'globalStyle' should be removed because it breaks react-modal-sheet's
scroll-lock gutter handling; instead stop overriding paddingRight and either add
the disableScrollLocking prop to your Sheet components (use the Sheet
component's disableScrollLocking attribute) so the library doesn't inject
padding, or ensure the scrollbar gutter is always present by setting body
overflow behavior at the global CSS level (e.g., apply an always-visible
vertical scrollbar via a global style like body { overflow-y: scroll }) to avoid
layout shift when modals open.
🧹 Nitpick comments (2)
src/components/carousel/popularCarousel/PopularCarousel.tsx (1)

67-69: 하드코딩된 슬라이드 너비 — CSS 값과 동기화 위험.

33.5 + 2popularCard.css.tsslideList width(33.5rem)와 gap(2rem)에서 유래한 매직 넘버입니다. CSS 값이 변경되면 이 계산도 함께 수정해야 하지만 놓치기 쉽습니다. 상수로 추출하여 CSS와 JS가 동일한 값을 참조하도록 하는 것을 권장합니다.

♻️ 상수 추출 제안
+// 상수 정의 (공유 파일 또는 컴포넌트 상단)
+const SLIDE_WIDTH_REM = 33.5;
+const SLIDE_GAP_REM = 2;
+
 // PopularCarousel 내부
 style={{
-  transform: `translateX(calc(-${currentIndex * (33.5 + 2)}rem + ${dragOffset}px))`,
+  transform: `translateX(calc(-${currentIndex * (SLIDE_WIDTH_REM + SLIDE_GAP_REM)}rem + ${dragOffset}px))`,
src/components/card/popularCard/popularCard.css.ts (1)

4-39: popularCarousel.css.ts와 스타일 정의 중복.

slideItem, imageWrapper, slideContent, templestayName 등의 스타일이 popularCarousel.css.ts에도 동일하게 정의되어 있습니다(popularCarousel.css.ts Lines 18-45 참고). 한쪽에서 스타일을 수정하면 다른 쪽과 불일치가 발생할 수 있으므로, 공통 스타일을 하나의 파일로 통합하거나 공유하는 것을 검토해 주세요.

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: 1

🤖 Fix all issues with AI agents
In `@src/components/banner/MainBanner.tsx`:
- Line 55: The onKeyDown handler in MainBanner.tsx currently triggers
handleBannerClick(banner) for Enter or Space but does not prevent the browser's
default behavior for Space (which can scroll the page); update the inline
handler (or the delegated key handler) to call e.preventDefault() when e.key ===
' ' (or e.code === 'Space') before invoking handleBannerClick(banner) so Space
activation doesn't cause page scroll while preserving Enter behavior.

Copy link
Collaborator

@bykbyk0401 bykbyk0401 left a comment

Choose a reason for hiding this comment

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

훅분리 좋네유~!!!

@bykbyk0401 bykbyk0401 merged commit 808ae3c into develop Feb 12, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FIX] 3차 스프린트 QA 2차

2 participants