Feat/#74 카테고리 별 맛집 페이지에서 스와이프로 카테고리 전환#76
Conversation
- 카테고리 별 맛집 페이지에서 스와이프로 카테고리 전환
- prevMapCenter.ts에서 lastMapCenter.ts로 이름 변경
Walkthrough맵 센터 위치 추적 기능을 추가하고 카테고리 선택 시 스와이프 제스처를 구현했습니다. 기존 Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant Campus as CampusSelector
participant Store as lastMapCenterStore
participant Map as MapComponent
User->>Campus: 캠퍼스 선택
activate Campus
Campus->>Store: setLastMapCenter(위치)
activate Store
Store->>Map: 상태 업데이트
deactivate Store
Map->>Map: 맵 센터 이동
deactivate Campus
Note over Campus,Map: 사용자가 캠퍼스를 변경하면<br/>해당 위치가 저장되어<br/>맵 페이지에서 복원 가능
sequenceDiagram
participant User as 사용자
participant Place as Places Component
participant Handler as onDragEnd Handler
participant Callback as setIdFunc
User->>Place: 드래그/스와이프
activate Place
Place->>Handler: onDragEnd 호출<br/>(offset, velocity)
activate Handler
Handler->>Handler: 스와이프 파워 계산<br/>threshold 확인
alt 스와이프 감지
Handler->>Callback: 새로운 ID로 호출<br/>(범위: 1-15)
end
deactivate Handler
deactivate Place
Note over Place,Callback: 수평 드래그 제스처로<br/>카테고리 전환 구현
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 추가 검토 필요 영역:
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/categories/[id]/CategoryDetailPage.tsx (1)
17-24:window.history.replaceState에null을 전달하면 Next Router 상태가 손상됩니다검증 결과, 현재 코드의 근본 문제는 다음과 같습니다:
window.history.replaceState(null, '', `/categories/${id}`)Next.js App Router는
window.history.state에 내부 라우터 상태를 저장하는데,null을 전달하면 이 상태가 덮어씌워집니다. 그 결과:
usePathname()이 제대로 업데이트되지 않을 수 있음- 브라우저 뒤로가기/앞으로가기 동작이 깨질 수 있음
- 라우터 상태 불일치로 인한 예기치 않은 렌더링 문제 발생
해결 방안 (권장):
useRouter().replace()를 사용하거나,window.history.state를 보존해서 호출합니다:-import { usePathname } from 'next/navigation' +import { usePathname, useRouter } from 'next/navigation' export const CategoryDetailPage = () => { + const router = useRouter() const { data: categories } = useSuspenseQuery(useCategoryQueries.list()) const activeCategoryId = usePathname().split('/')[2] || '0' const setIdFunc = (id: string) => { - window.history.replaceState(null, '', `/categories/${id}`) + router.replace(`/categories/${id}`, { scroll: false }) }이렇게 하면 Next Router 상태와
usePathname()이 동기화되어, 스와이프 시 Places와 RowCategories 모두 올바른 카테고리로 재렌더링됩니다.lines 17-24, 45-52
🧹 Nitpick comments (2)
apps/web/app/categories/[id]/_components/Places/Places.tsx (2)
17-47: 스와이프 ID 증감 로직의 하드코딩된 범위(1~15)와 숫자 변환은 향후 변경에 취약할 수 있습니다
currentCategoryId를Number(id)로 파싱하고, 범위를1~15로 고정해 두어서 카테고리 개수나 ID 스키마가 바뀌면 이 파일만 따로 수정해야 합니다.- 또한 id가 숫자가 아닌 문자열(슬러그 등)로 변경되면
Number(id)가NaN이 되어 비교가 전부 실패하고, 스와이프가 묵살되는 문제가 생길 수 있습니다.카테고리 범위와 타입이 고정이라면 지금도 동작은 맞지만, 유지보수성을 위해서:
- 카테고리 최소/최대 ID를 상수로 선언해 import 하거나, 상위에서 prop으로 전달받는 형태로 magic number(1, 15)를 제거하는 것,
- 혹은
id타입을type CategoryId = '1' | '2' | ...처럼 좁혀 두거나,Number(id)결과가NaN일 때는 조기 return 하는 방어 로직을 고려해 보면 좋겠습니다.
2-3: 스와이프 임계값 미만 드래그 후 리스트가 옆으로 살짝 밀린 채 남지 않는지 확인이 필요합니다
motion.div에drag='x'만 걸려 있고 별도의 제약이나 복귀 애니메이션이 없어서,SWIPE_CONFIDENCE_THRESHOLD(50)를 넘지 못한 짧은 드래그 이후에도 콘텐츠가 약간 좌우로 이동된 상태로 남을 수 있습니다. 그렇게 되면 스와이프에 실패했는데도 리스트가 한쪽으로 치우쳐 보이는 어색한 UX가 나올 수 있습니다.실제 기기에서 한번 확인해 보시고, 문제가 있다면 예를 들어:
dragConstraints로 이동 가능한 범위를 제한하거나,onDragEnd안에서 항상x: 0으로 되돌리는 애니메이션을 트리거하는 방식으로 드래그 실패 시에는 원위치로 자연스럽게 복귀시키는 걸 검토해 보시면 좋겠습니다.
Also applies to: 65-75
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
apps/web/app/_components/CampusSelector/CampusSelector.tsx(1 hunks)apps/web/app/_components/eventBanners/FoodSlotMachineBanner.tsx(1 hunks)apps/web/app/_components/eventBanners/LuckyDrawBanner.tsx(1 hunks)apps/web/app/_store/lastMapCenter.ts(1 hunks)apps/web/app/_store/prevMapCenter.ts(0 hunks)apps/web/app/categories/[id]/CategoryDetailPage.tsx(1 hunks)apps/web/app/categories/[id]/_components/Places/EmptyPlaces.tsx(1 hunks)apps/web/app/categories/[id]/_components/Places/Places.tsx(1 hunks)apps/web/app/categories/template.tsx(0 hunks)apps/web/app/map/MapComponent.tsx(1 hunks)
💤 Files with no reviewable changes (2)
- apps/web/app/categories/template.tsx
- apps/web/app/_store/prevMapCenter.ts
🧰 Additional context used
🧬 Code graph analysis (3)
apps/web/app/categories/[id]/CategoryDetailPage.tsx (1)
apps/web/app/categories/[id]/_components/Places/Places.tsx (1)
Places(17-77)
apps/web/app/_components/CampusSelector/CampusSelector.tsx (3)
apps/web/app/_store/campus.ts (1)
useCampusStore(9-12)apps/web/app/_store/lastMapCenter.ts (1)
useLastMapCenterStore(20-23)apps/web/app/_constants/campus.ts (1)
CAMPUS_LOCATION(9-13)
apps/web/app/categories/[id]/_components/Places/Places.tsx (5)
apps/web/app/_store/campus.ts (1)
useCampusStore(9-12)apps/web/app/_apis/queries/place.ts (1)
usePlaceQueries(27-69)apps/web/app/categories/[id]/_components/Places/EmptyPlaces.tsx (1)
EmptyPlaces(9-103)packages/ui/src/components/Layout/VerticalScrollArea/VerticalScrollArea.tsx (1)
VerticalScrollArea(4-20)apps/web/app/_components/PlaceListItem/PlaceListItem.tsx (1)
PlaceListItem(15-55)
🔇 Additional comments (6)
apps/web/app/_components/eventBanners/LuckyDrawBanner.tsx (1)
23-27: 배너 본문 타이포그래피 조정 일관성 좋습니다.기존 구조는 그대로 두고 폰트 사이즈만 조정하면서 sm 이상에서만 크게 보이도록 처리해, 다른 배너들과의 시각적 일관성이 잘 맞습니다.
apps/web/app/_components/eventBanners/FoodSlotMachineBanner.tsx (1)
22-26: 슬롯머신 배너 텍스트도 LuckyDraw와 일관되게 정리됨폰트 크기/굵기와 반응형 클래스 구성이 LuckyDrawBanner와 동일 패턴이라, 두 이벤트 배너 간 UI 톤이 잘 맞습니다.
apps/web/app/categories/[id]/_components/Places/EmptyPlaces.tsx (1)
80-80: Empty 상태를 화면 전체 기준으로 중앙 정렬하도록 바뀐 점 좋습니다상위가 h-full을 갖는 컨테이너라는 전제에서, auto margin 대신 전체 높이 + justify-center를 쓰는 편이 뷰포트 높이 변화에도 더 안정적으로 동작할 것 같습니다.
apps/web/app/_components/CampusSelector/CampusSelector.tsx (1)
13-31: 캠퍼스 변경 시 맵 센터를 즉시 동기화하는 로직이 요구사항에 잘 맞습니다
isCampusType으로 키를 안전하게 좁힌 뒤setLastMapCenter(CAMPUS_LOCATION[newKey])를 호출해서, 이후 맵 진입 시 선택한 캠퍼스 중심으로 바로 초기화될 수 있게 잘 연결돼 있습니다.setTimeout으로 캠퍼스 상태를 지연 설정하는 기존 패턴과도 충돌 없이 동작할 것으로 보입니다.apps/web/app/map/MapComponent.tsx (1)
7-35: lastMapCenter 스토어 경로 변경으로 구현이 새 스토어와 잘 정합됩니다
useLastMapCenterStore를 새 파일(@/_store/lastMapCenter)에서 불러오되,lastMapCenter || CAMPUS_LOCATION[campus]기본 센터 계산과 언마운트 시setLastMapCenter호출 로직은 그대로라 기존 동작을 그대로 유지하면서 스토어만 교체했습니다.apps/web/app/_store/lastMapCenter.ts (1)
1-23: 지도의 마지막 중심 좌표를 위한 최소한의 Zustand 스토어 설계가 명확합니다
Coord | null을 허용하는 단일 상태와 setter만 노출해서 MapComponent/CampusSelector에서 읽고 쓰기 쉬운 구조입니다. 주석에 주요 사용 시나리오(이탈/복귀, 캠퍼스 변경 시 처리)가 잘 설명돼 있어 다른 개발자가 용도를 이해하기도 좋겠습니다.
#️⃣연관된 이슈
📝작업 내용
1. 카테고리 별 맛집 리스트 스와이프 전환 구현
목적:
모바일 배달 앱(예: 배민)과 유사한 네이티브 사용자 경험을 웹에서도 제공
구현 내용:
motion/react을 사용하여 좌우 드래그 제스처를 감지하고 적용했습니다.2. 캠퍼스 변경 시 지도 중심 좌표 로직 개선
변경 내용:
메인 화면에서 캠퍼스(천안/공주/예산)를 변경할 때, 해당 캠퍼스에 맞는 초기 위치로 지도가 설정되도록 useLastMapCenterStore의 상태 업데이트 로직을 수정했습니다.
효과:
캠퍼스를 변경하고 지도로 진입했을 때, 엉뚱한 위치(이전 마지막 좌표)가 아닌 선택한 캠퍼스의 중심을 바로 보여줍니다.
스크린샷 (선택)
2025-11-24.3.55.03.mov
💬리뷰 요구사항(선택)
Summary by CodeRabbit
릴리스 노트
새로운 기능
스타일
✏️ Tip: You can customize this high-level summary in your review settings.