✨ Feat: course result 페이지 및 지도 페이지 구현#64
Conversation
Walkthrough코스 결과 페이지와 관련 컴포넌트(풀스크린 맵, 결과 리스트/맵, 태그), 카카오 맵 SDK 로더·훅, 정적 장소 데이터 및 아이콘/타입 선언을 추가합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Page as CourseResultPage
participant TagGroup
participant ResultList
participant ResultMap
participant FullMap
participant Kakao as KakaoSDK
User->>Page: 페이지 진입
Page->>TagGroup: 렌더(viewMode, onToggleView)
alt viewMode == list
Page->>ResultList: 렌더(listData)
else viewMode == map
Page->>ResultMap: 렌더(MAP_LOCATIONS)
ResultMap->>Kakao: loadKakaoSdk → useKakaoMap(mapRef, MAP_LOCATIONS)
Kakao-->>ResultMap: 마커·인포윈도우 초기화
end
User->>TagGroup: 뷰 토글 클릭
TagGroup->>Page: onToggleView()
Page->>ResultMap: 뷰 전환
User->>ResultMap: "전체화면 보기" 클릭
ResultMap->>Page: router.push('/map/result/Map?from=map')
Page->>FullMap: FullMap 렌더 → useKakaoMap(mapRef, MAP_LOCATIONS)
Kakao-->>FullMap: 맵 초기화 완료
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ 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 |
|
🏷️ Labeler has automatically applied labels based on your PR title, branch name, or commit message. |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (6)
src/styles/globals.css (1)
222-222: 커스텀border-pink-300유틸의 의미 충돌 가능성현재 클래스가 두 역할(두께+스타일+색상)을 동시에 수행합니다. Tailwind 관례(색상 전용:
border-pink-300, 두께 전용:border)와 다르게 동작해 혼선이 생길 수 있습니다.
권장: 기본 조합(border border-pink-300)을 사용하거나, 동일 스케일 전반(border-pink-50~900)을 일관되게 제공하는border-color전용 유틸을 정의하세요.src/pages/map/result/Map.tsx (1)
7-17: 전역 객체Map과의 섀도잉 회피 권장컴포넌트명을
Map으로 두면 JS 전역Map과 섀도잉됩니다.CourseResultMapPage또는MapPage로 변경을 권장합니다.
예시:-const Map = () => { +const MapPage = () => { ... -export default Map; +export default MapPage;src/shared/constants/map/result/mapLocations.ts (1)
1-7: 데이터 타입 명시로 안전성 강화위치 데이터에 타입을 부여해 오타/필드 누락을 컴파일 타임에 검출하세요.
-export const MAP_LOCATIONS = [ +type Location = { name: string; lat: number; lng: number }; +export const MAP_LOCATIONS: Location[] = [ { name: '한국만화박물관', lat: 37.505652, lng: 126.776922 }, ... -]; +];또는 TS 4.9+
satisfies:export const MAP_LOCATIONS = [ ... ] satisfies readonly { name: string; lat: number; lng: number }[];src/pages/map/result/components/TagGroup.tsx (1)
12-12: 향후 태그 데이터를 props로 받는 것을 고려하세요.현재 태그가 하드코딩되어 있습니다. 이슈 #61의 체크리스트에 "태그와 카드 연결"이 남아있는 만큼, 향후 API 연동 시 tags를 props로 전달받도록 리팩토링하면 재사용성이 향상됩니다.
src/pages/map/result/index.tsx (1)
35-40:ControlBar의 하드코딩된 props를 동적으로 처리하세요.
isLoggedIn={false}와 빈onLogin핸들러가 하드코딩되어 있습니다. 실제 인증 로직과 연동하거나, 추후 작업이 필요하다면 TODO 코멘트를 추가하는 것을 권장합니다.src/shared/hooks/kakao/useKakaoMap.ts (1)
33-35: InfoWindow 컨텐츠에 인라인 스타일 대신 CSS 클래스 사용을 고려하세요.인라인 스타일을 문자열로 주입하고 있습니다. 향후 스타일 일관성과 유지보수를 위해 CSS 클래스나 Tailwind 유틸리티를 사용하는 것을 권장합니다.
예시:
const infoWindow = new window.kakao.maps.InfoWindow({ - content: `<div style="padding:5px 10px; font-size:12px;">${p.name}</div>`, + content: `<div class="kakao-info-window">${p.name}</div>`, });그리고 전역 CSS 또는 globals.css에 스타일 정의:
.kakao-info-window { padding: 0.5rem 1rem; font-size: 1.2rem; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
public/assets/bannerMap.svgis excluded by!**/*.svgpublic/assets/sample1.jpgis excluded by!**/*.jpgsrc/shared/icons/source/ListButton.svgis excluded by!**/*.svgsrc/shared/icons/source/Stamp.svgis excluded by!**/*.svg
📒 Files selected for processing (14)
src/pages/map/result/Map.tsx(1 hunks)src/pages/map/result/components/FullMap.tsx(1 hunks)src/pages/map/result/components/ResultList.tsx(1 hunks)src/pages/map/result/components/ResultMap.tsx(1 hunks)src/pages/map/result/components/TagGroup.tsx(1 hunks)src/pages/map/result/index.tsx(1 hunks)src/shared/constants/map/result/listData.ts(1 hunks)src/shared/constants/map/result/mapLocations.ts(1 hunks)src/shared/hooks/kakao/useKakaoMap.ts(1 hunks)src/shared/icons/iconNames.ts(1 hunks)src/shared/icons/index.ts(1 hunks)src/shared/types/kakao.d.ts(1 hunks)src/shared/utils/loadKakaoSdk.ts(1 hunks)src/styles/globals.css(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
src/pages/map/result/index.tsx (6)
src/pages/map/result/components/TagGroup.tsx (1)
TagGroup(11-34)src/shared/lib/utils.ts (1)
cn(71-73)src/pages/map/result/components/ResultList.tsx (1)
ResultList(7-46)src/pages/map/result/components/ResultMap.tsx (1)
ResultMap(7-38)src/shared/components/tab/BottomNav.tsx (1)
BottomNav(21-54)src/shared/components/container/Popup.tsx (1)
Popup(9-32)
src/pages/map/result/components/ResultMap.tsx (2)
src/shared/hooks/kakao/useKakaoMap.ts (1)
useKakaoMap(11-46)src/shared/constants/map/result/mapLocations.ts (1)
MAP_LOCATIONS(1-7)
src/pages/map/result/components/ResultList.tsx (2)
src/shared/lib/utils.ts (1)
cn(71-73)src/shared/constants/map/result/listData.ts (1)
listData(1-65)
src/shared/hooks/kakao/useKakaoMap.ts (1)
src/shared/utils/loadKakaoSdk.ts (1)
loadKakaoSdk(1-16)
src/pages/map/result/components/TagGroup.tsx (1)
src/shared/lib/utils.ts (1)
cn(71-73)
src/pages/map/result/components/FullMap.tsx (2)
src/shared/hooks/kakao/useKakaoMap.ts (1)
useKakaoMap(11-46)src/shared/constants/map/result/mapLocations.ts (1)
MAP_LOCATIONS(1-7)
src/pages/map/result/Map.tsx (1)
src/pages/map/result/components/FullMap.tsx (1)
FullMap(7-19)
🪛 Biome (2.1.2)
src/pages/map/result/Map.tsx
[error] 7-7: Do not shadow the global "Map" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-deploy
🔇 Additional comments (7)
src/shared/icons/index.ts (1)
14-14: 아이콘 추가 반영 확인됨
ListButton.svg임포트가 아이콘 목록과 일치합니다. 문제 없습니다.src/shared/icons/iconNames.ts (1)
14-14: 아이콘 네임 추가 LGTM
"ListButton"추가가 인덱스 임포트와 일치합니다. 사용처(예: TagGroup)에서도 동일 문자열인지 한 번만 확인해 주세요.src/pages/map/result/components/FullMap.tsx (1)
7-19: LGTM!접근성 속성(role, aria-label)이 적절히 설정되어 있고, 컴포넌트 구조가 명확합니다.
src/pages/map/result/index.tsx (2)
4-4: Next.js App Router 사용 시next/navigation으로 마이그레이션하세요.
next/router를 사용하고 있는데,'use client'컴포넌트에서 App Router를 사용한다면next/navigation의useRouter와useSearchParams를 사용해야 합니다.프로젝트가 App Router를 사용하는지 확인하고, 사용한다면 다음과 같이 수정하세요:
-import { useRouter } from 'next/router'; +import { useRouter, useSearchParams } from 'next/navigation'; export default function CourseResultPage() { const router = useRouter(); + const searchParams = useSearchParams(); const [showPopup, setShowPopup] = useState(false); const [viewMode, setViewMode] = useState<'list' | 'map'>('list'); useEffect(() => { // ... localStorage logic - if (router.query.from === 'map') { + if (searchParams.get('from') === 'map') { setViewMode('map'); } - }, [router.query.from]); + }, [searchParams]);Pages Router를 사용 중이라면 이 코멘트는 무시하셔도 됩니다.
Also applies to: 26-28
65-74: 뷰 전환 시 상태 관리가 적절합니다.
viewMode상태를 기반으로 한 조건부 렌더링과 스타일링이 명확하고, 리스트/지도 뷰 간 전환이 자연스럽게 처리되고 있습니다.src/shared/hooks/kakao/useKakaoMap.ts (1)
11-46: 전체적인 훅 구조와 카카오 지도 통합이 잘 설계되었습니다.
useKakaoMap훅이 지도 초기화, 마커 생성, 이벤트 리스너 등록을 명확하게 처리하고 있으며, React의 생명주기와 잘 통합되어 있습니다. 의존성 배열도 적절히 설정되어 있습니다.src/shared/constants/map/result/listData.ts (1)
1-65: 이미지 파일이 존재하므로 추가 조치가 필요하지 않습니다.검증 결과
public/assets/sample1.jpg파일이 실제로 존재하며, 코드에서 참조하는 경로(/assets/sample1.jpg)가 올바르게 구성되어 있습니다. 해당 이미지 파일에 대한 우려사항은 해결되었습니다.
| <style jsx global>{` | ||
| /* Chrome, Safari, Opera */ | ||
| ::-webkit-scrollbar { | ||
| width: 0 !important; | ||
| height: 0 !important; | ||
| } | ||
|
|
||
| /* Firefox */ | ||
| * { | ||
| scrollbar-width: none !important; | ||
| -ms-overflow-style: none !important; | ||
| } | ||
| `}</style> |
There was a problem hiding this comment.
전역 스크롤바 숨김은 접근성 저해—컨테이너 단위로 한정하세요
<style jsx global>로 전역 ::-webkit-scrollbar/* { scrollbar-width: none }를 막아 앱 전반 스크롤바가 사라집니다. 스크린리더/키보드 사용자에게 치명적입니다. 래퍼에 한정된 클래스 적용으로 범위를 축소하세요.
- <style jsx global>{`
- /* Chrome, Safari, Opera */
- ::-webkit-scrollbar {
- width: 0 !important;
- height: 0 !important;
- }
-
- /* Firefox */
- * {
- scrollbar-width: none !important;
- -ms-overflow-style: none !important;
- }
- `}</style>
+ <style jsx>{`
+ .no-scrollbar::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+ }
+ .no-scrollbar {
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ }
+ `}</style>그리고 래퍼에 클래스 추가:
- className={cn(
+ className={cn(
'grid grid-cols-2 gap-[1.2rem] w-full',
'justify-items-center pb-[15rem]',
- 'overflow-y-scroll'
+ 'overflow-y-scroll no-scrollbar'
)}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/pages/map/result/components/ResultList.tsx around lines 31 to 43, the
current <style jsx global> hides scrollbars globally which breaks accessibility;
instead scope the scrollbar rules to the component wrapper (e.g. add a className
like "resultListWrapper" to the outermost wrapper element) and move the CSS out
of global so selectors target .resultListWrapper ::-webkit-scrollbar and
.resultListWrapper { scrollbar-width: none; -ms-overflow-style: none; } (remove
the global * and avoid unnecessary !important), ensuring only this container
hides its scrollbars.
| @@ -0,0 +1,38 @@ | |||
| 'use client'; | |||
| import { useRef } from 'react'; | |||
| import { useRouter } from 'next/router'; | |||
There was a problem hiding this comment.
Next.js App Router에서는 next/navigation을 사용하세요.
'use client' 컴포넌트에서 next/router를 import하고 있습니다. Next.js 13+ App Router를 사용하는 경우 next/navigation의 useRouter를 사용해야 합니다.
다음과 같이 수정하세요:
-import { useRouter } from 'next/router';
+import { useRouter } from 'next/navigation';📝 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.
| import { useRouter } from 'next/router'; | |
| import { useRouter } from 'next/navigation'; |
🤖 Prompt for AI Agents
In src/pages/map/result/components/ResultMap.tsx around line 3, the component
currently imports useRouter from 'next/router' which is incorrect for App Router
client components; replace that import to use useRouter from 'next/navigation'
(ensure the file has 'use client' at the top if it’s a client component), update
any router method calls that differ between the two hooks to their
next/navigation equivalents, and remove the old 'next/router' import.
There was a problem hiding this comment.
저희는 page Router이라 next/router가 맞을거같아요 하지만 'use client';는 수정해야할 것 같네요
There was a problem hiding this comment.
@jjangminii 네, 맞습니다! Pages Router를 사용하시는 거였군요. 정정해주셔서 감사합니다.
Pages Router에서는:
- ✅
next/router사용이 맞습니다 - ❌
'use client'지시문은 App Router 전용이므로 제거해야 합니다
Pages Router에서는 pages/ 디렉토리 하위의 모든 컴포넌트가 자동으로 클라이언트 측 라우팅을 지원하므로 'use client' 지시문이 필요하지 않습니다. 이 지시문은 Next.js 13+ App Router의 Server Components와 Client Components를 구분하기 위한 것입니다.
해당 파일(src/pages/map/result/components/ResultMap.tsx)에서 첫 줄의 'use client';를 제거하시면 됩니다.
✏️ Learnings added
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.305Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| declare global { | ||
| interface Window { | ||
| kakao: typeof kakao; | ||
| } | ||
| } | ||
| export {}; |
There was a problem hiding this comment.
TS 전역 타입 오류 위험: typeof kakao는 외부 선언 없으면 실패
현재 typeof kakao는 Kakao SDK가 타입을 제공하지 않으면 컴파일 에러를 유발합니다. 최소 안전 타입으로 교체하세요.
-declare global {
- interface Window {
- kakao: typeof kakao;
- }
-}
-export {};
+declare global {
+ interface Window {
+ kakao?: {
+ maps?: unknown; // 필요 시 세부 타입 확장
+ };
+ }
+}
+export {};추후 필요 범위에 맞춰 maps.Map, LatLng, Marker, InfoWindow, event.addListener 등만 정밀 선언하면 DX가 개선됩니다.
📝 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.
| declare global { | |
| interface Window { | |
| kakao: typeof kakao; | |
| } | |
| } | |
| export {}; | |
| declare global { | |
| interface Window { | |
| kakao?: { | |
| maps?: unknown; // 필요 시 세부 타입 확장 | |
| }; | |
| } | |
| } | |
| export {}; |
🤖 Prompt for AI Agents
In src/shared/types/kakao.d.ts lines 1-6, the Window.kakao property is typed as
"typeof kakao" which fails if there is no external kakao declaration; replace
that with a safe minimal type (e.g., any or an explicit minimal interface) to
avoid TS compile errors, then optionally add more specific declarations later
for maps.Map, LatLng, Marker, InfoWindow, and event.addListener as needed.
| if (existing) { | ||
| callback(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
스크립트 로딩 완료 여부를 확인해야 합니다.
스크립트 태그가 이미 존재하는 경우 즉시 callback을 호출하지만, 이 시점에 SDK 로딩이 완료되지 않았을 수 있습니다. 이로 인해 useKakaoMap 훅에서 window.kakao에 접근할 때 경쟁 상태(race condition)가 발생할 수 있습니다.
다음과 같이 수정하여 SDK 로딩 상태를 확인하세요:
if (existing) {
- callback();
+ if (window.kakao && window.kakao.maps) {
+ callback();
+ } else {
+ existing.addEventListener('load', callback);
+ }
return;
}📝 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.
| if (existing) { | |
| callback(); | |
| return; | |
| } | |
| if (existing) { | |
| if (window.kakao && window.kakao.maps) { | |
| callback(); | |
| } else { | |
| existing.addEventListener('load', callback); | |
| } | |
| return; | |
| } |
🤖 Prompt for AI Agents
In src/shared/utils/loadKakaoSdk.ts around lines 5 to 8, the current code calls
callback immediately when a script tag already exists, but that may be before
the SDK has finished loading and causes a race when accessing window.kakao;
update the logic so that if the script tag already exists you check whether
window.kakao is present and ready and call callback immediately only if it is,
otherwise attach an onload (and onerror) handler to that existing script to
invoke the callback once the SDK finishes loading (and handle/report load
failure); similarly when creating a new script, ensure you set onload/onerror
handlers and only call callback after successful load.
KongMezu
left a comment
There was a problem hiding this comment.
수고하셨습니다..! 전체적으로 기존 만들어둔 유틸과 컴포넌트 사용에 대한 리뷰 남겨놨습니다! 확인해보시고 수정해보심 좋을 거 같습니다!
src/pages/map/result/index.tsx
Outdated
| {showPopup && ( | ||
| <div className="fixed inset-0 bg-black/40 z-50 flex items-center justify-center"> |
There was a problem hiding this comment.
이 부분 shared/shared/set/PopupSet.tsx 에
오버레이랑 같이 뜨도록 해놓은거 있어서
이거 이용하심 될 거 같습니다!
팝업 PR
| /* Firefox */ | ||
| * { | ||
| scrollbar-width: none !important; | ||
| -ms-overflow-style: none !important; | ||
| } | ||
| `}</style> |
There was a problem hiding this comment.
스크롤바 숨김 경우 제가 최근에 올린 PR에서
global.css 로 빼놔서 확인해보시고, 머지되면 리팩토링 해보시면 될 거 같습니다!
| role="region" | ||
| aria-labelledby="map-title" | ||
| > | ||
| <h2 id="map-title" className="sr-only"> |
There was a problem hiding this comment.
여기 피그마에서 정해진 폰트 있지 않나요??
유틸 클래스 명시 없이 h2태그로만 묶어도 괜찮나요?
src/styles/globals.css
Outdated
| @utility max-w-mobile { max-width: var(--max-w-mobile); } | ||
| @utility min-w-100 { min-width: var(--min-w-100); } | ||
|
|
||
| @utility border-pink-300 { border: 1px solid var(--color-pink-300); } |
There was a problem hiding this comment.
이 부분
전역 유틸리티로 등록하지 않아도 될 것 같습니다..!
필요한 클래스나 컴포넌트 내부에서 선언해도 충분히 동작할 것 같습니다!
src/pages/map/result/Map.tsx
Outdated
|
|
||
| const Map = () => { | ||
| return ( | ||
| <div className="w-full h-[100vh] bg-[#46d1cd] overflow-hidden"> |
There was a problem hiding this comment.
이 부분 bg-mint-300 으로 유틸 설정해놔서 이거 쓰심 될 거 같습니다!
| useEffect(() => { | ||
| const hasSeenPopup = localStorage.getItem('seenCoursePopup'); | ||
| if (!hasSeenPopup) { | ||
| setShowPopup(true); | ||
| localStorage.setItem('seenCoursePopup', 'true'); | ||
| } | ||
|
|
||
| if (router.query.from === 'map') { | ||
| setViewMode('map'); | ||
| } | ||
| }, [router.query.from]); |
There was a problem hiding this comment.
query 체크랑 localstorage 체크 이렇게 2개 useeffect로 분리하는게 의존성 문제에서도 더 좋을 거 같습니다!
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
jjangminii
left a comment
There was a problem hiding this comment.
고생하셨습니다-! 코멘트 한번 체크해주시고 코드래빗의 'use client' 부분 한번 더 확인해주시면 좋을 것 같아요
| 'use client'; | ||
|
|
||
| import { cn } from '@/shared/lib'; | ||
| import LocationCard from '@/shared/components/container/LocationCard'; |
There was a problem hiding this comment.
이거 임포트 경로 @/shared/components 만 써도 됩니다
| <style jsx global>{` | ||
| /* Chrome, Safari, Opera */ | ||
| ::-webkit-scrollbar { | ||
| width: 0 !important; | ||
| height: 0 !important; | ||
| } |
There was a problem hiding this comment.
이거 수민님이 따로 global.css에서 작업항걸로 아는데 한번 확인해주세요
| @@ -0,0 +1,38 @@ | |||
| 'use client'; | |||
| import { useRef } from 'react'; | |||
| import { useRouter } from 'next/router'; | |||
There was a problem hiding this comment.
저희는 page Router이라 next/router가 맞을거같아요 하지만 'use client';는 수정해야할 것 같네요
| @@ -0,0 +1,34 @@ | |||
| 'use client'; | |||
|
|
|||
| import Tag from '@/shared/components/tag/Tag'; | |||
src/pages/map/result/index.tsx
Outdated
| import { BottomNav } from '@/shared/components/tab/BottomNav'; | ||
| import Popup from '@/shared/components/container/Popup'; |
| const infoWindow = new window.kakao.maps.InfoWindow({ | ||
| content: `<div style="padding:5px 10px; font-size:12px;">${p.name}</div>`, | ||
| }); |
There was a problem hiding this comment.
이부분은 어떤 역할인가요? 왜 px로 되어잇는지 궁금합니다
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/styles/globals.css (1)
221-221: 이전 리뷰 의견 재검토: 지역 범위 사용 가능성 확인이전 리뷰에서 지적된 바와 같이
border-pink-300을 전역 유틸리티로 정의할 필요가 있는지 확인해주세요.해당 유틸리티가:
- 여러 컴포넌트(ResultList, ResultMap, TagGroup 등)에서 사용되는 경우 → 전역 유틸리티 유지 ✓
- 1~2개 컴포넌트에만 사용되는 경우 → 해당 컴포넌트 파일 내 로컬 스타일로 이동 권장
필요하다면 사용 위치를 확인하고 일관된 스타일 관리 전략을 고려해주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/shared/icons/iconNames.ts(1 hunks)src/shared/icons/index.ts(1 hunks)src/styles/globals.css(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/shared/icons/iconNames.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/shared/icons/index.ts (2)
src/shared/icons/scripts/generate-icon-list.ts (2)
file(24-24)file(16-16)src/shared/icons/components/icon.tsx (2)
IconProps(42-53)rest(55-110)
🔇 Additional comments (2)
src/styles/globals.css (1)
223-224: no-scrollbar 규칙 적용 승인스크롤바 숨김 규칙이 브라우저 호환성을 잘 처리합니다(webkit, MS, 표준). 지도 결과 페이지의 ResultList 컴포넌트 등에서 스크롤바를 숨기는 PR 목표에 부합합니다.
src/shared/icons/index.ts (1)
13-13: ListButton 아이콘 통합 상태 확인됨 - 생성 방식 수동 검증 권장기능적 통합은 모두 정상적으로 이루어졌습니다:
- ✓
src/shared/icons/source/ListButton.svg파일 존재- ✓
iconNames.ts에 'ListButton' 타입 포함됨- ✓
TagGroup.tsx에서 정상 사용 중- ✓ import 형식 및 알파벳 순서 정확함
다만 파일 헤더에 "자동 생성 파일"로 명시되어 있으므로, 개발 환경에서 다음을 수동으로 확인해 주시기 바랍니다:
src/shared/icons/scripts/generate-icon-list.ts스크립트 실행 시 index.ts에 ListButton import가 자동으로 생성되는지 확인- 향후 유지보수 시 수동 편집을 피하고 스크립트를 통한 재생성으로 덮어쓰기 문제를 방지할 수 있습니다
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/pages/map/result/Map.tsx (2)
2-2: useRouter 훅 사용으로 변경 필요 (미해결 이슈)이전 리뷰에서 지적된 사항이 아직 반영되지 않았습니다. Pages Router 환경에서는
useRouter훅을 사용하는 것이 권장됩니다.다음과 같이 수정해 주세요:
-import router from 'next/router'; +import { useRouter } from 'next/router'; const Map = () => { + const router = useRouter(); return (Based on learnings
Also applies to: 10-10
7-7: 유틸리티 클래스 사용 권장팀원(KongMezu)이 제안한 대로 하드코딩된 색상값 대신
bg-mint-300유틸리티 클래스를 사용하는 것이 좋습니다. 이는 디자인 시스템의 일관성을 유지하고 유지보수성을 향상시킵니다.- <div className="w-full h-[100vh] bg-[#46d1cd] overflow-hidden"> + <div className="w-full h-[100vh] bg-mint-300 overflow-hidden">
🧹 Nitpick comments (2)
src/styles/globals.css (1)
222-223:.no-scrollbar크로스 브라우저 호환성 개선.WebKit 브라우저(Chrome, Safari) 외에도 Firefox(
scrollbar-width)와 IE/Edge(-ms-overflow-style) 스크롤바 숨김을 완성했습니다. 새로운 map/result UI 컴포넌트에서 일관된 scrollbar 처리가 필요한 경우 적절한 변경입니다.단, 이전 리뷰에서 지적된 대로 이 클래스가 전역 유틸리티로 등록되어야 하는지는 재고할 가치가 있습니다. 현재 PR에서 실제로 여러 컴포넌트(ResultList, FullMap 등)가
.no-scrollbar를 재사용한다면, 전역 CSS 등록이 DRY 원칙을 따르므로 현재 구조는 합리적입니다. 만약 특정 컴포넌트에서만 사용되거나 미래에 확장성이 제한적이면, 컴포넌트별 scoped style이나 Tailwind 커스텀 유틸리티(@layer utilities)를 고려해 보세요.혹시 이 PR의 새로운 컴포넌트 중에서
.no-scrollbar를 사용하는 파일이 있다면 확인해 주세요. 여러 곳에서 재사용된다면 현재 전역 등록 접근이 최적입니다.src/pages/map/result/Map.tsx (1)
5-5: 컴포넌트명 명확성 개선 고려
Map이라는 이름이 JavaScript의 전역Map객체와 충돌할 수 있습니다. 더 명확한 이름(예:MapPage,CourseMapPage)을 사용하면 코드의 가독성과 명확성이 향상됩니다.-const Map = () => { +const MapPage = () => { return ( // ... ); }; -export default Map; +export default MapPage;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/pages/map/index.tsx(0 hunks)src/pages/map/result/Map.tsx(1 hunks)src/pages/map/result/components/ResultList.tsx(1 hunks)src/pages/map/result/components/ResultMap.tsx(1 hunks)src/pages/map/result/index.tsx(1 hunks)src/shared/hooks/kakao/useKakaoMap.ts(1 hunks)src/styles/globals.css(1 hunks)
💤 Files with no reviewable changes (1)
- src/pages/map/index.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- src/pages/map/result/components/ResultList.tsx
- src/pages/map/result/index.tsx
- src/shared/hooks/kakao/useKakaoMap.ts
- src/pages/map/result/components/ResultMap.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-31T17:34:04.305Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.305Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/map/result/Map.tsx
🧬 Code graph analysis (1)
src/pages/map/result/Map.tsx (1)
src/pages/map/result/components/FullMap.tsx (1)
FullMap(7-19)
🪛 Biome (2.1.2)
src/pages/map/result/Map.tsx
[error] 5-5: Do not shadow the global "Map" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-deploy
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/pages/map/result/index.tsx (1)
17-23: SSR 환경에서 localStorage 접근 시 안전성 검사가 여전히 필요합니다.
useEffect내에서localStorage에 직접 접근하고 있어 SSR 중 hydration 오류가 발생할 수 있습니다. 이전 리뷰에서 지적된 사항이 아직 해결되지 않았습니다.다음과 같이 수정하세요:
useEffect(() => { + if (typeof window === 'undefined') return; + const hasSeenPopup = localStorage.getItem('seenCoursePopup'); if (!hasSeenPopup) { setShowPopup(true); localStorage.setItem('seenCoursePopup', 'true'); } }, []);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/pages/map/result/components/ResultList.tsx(1 hunks)src/pages/map/result/components/TagGroup.tsx(1 hunks)src/pages/map/result/index.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/pages/map/result/components/TagGroup.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-31T17:34:04.305Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.305Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/map/result/index.tsx
🧬 Code graph analysis (2)
src/pages/map/result/components/ResultList.tsx (2)
src/shared/lib/utils.ts (1)
cn(71-73)src/shared/constants/map/result/listData.ts (1)
listData(1-65)
src/pages/map/result/index.tsx (5)
src/pages/map/result/components/TagGroup.tsx (1)
TagGroup(11-34)src/shared/lib/utils.ts (1)
cn(71-73)src/pages/map/result/components/ResultList.tsx (1)
ResultList(7-31)src/pages/map/result/components/ResultMap.tsx (1)
ResultMap(6-33)src/shared/components/set/PopupSet.tsx (1)
PopupSet(11-35)
🔇 Additional comments (4)
src/pages/map/result/components/ResultList.tsx (1)
7-31: 컴포넌트 구조와 접근성 구현이 잘 되어 있습니다.
role="list"와aria-label을 통한 적절한 접근성 마크업no-scrollbar클래스를 사용하여 이전 리뷰에서 지적된 전역 스크롤바 숨김 문제 해결- 각 아이템에
role="listitem"적용으로 의미론적 구조 유지- 반응형 그리드 레이아웃과 명확한 데이터 매핑
src/pages/map/result/index.tsx (3)
35-40: ControlBar의 하드코딩된 props를 확인해주세요.현재
isLoggedIn={false}, 빈onLogin핸들러, 하드코딩된userName="글다"로 설정되어 있습니다.이것이 의도된 것인지 확인이 필요합니다:
- 추후 실제 인증 로직과 연동 예정인가요?
- 현재 단계에서는 mock/placeholder로 사용하는 것이 맞나요?
실제 사용자 인증 상태를 반영하도록 개선이 필요하다면 알려주시면 구현을 도와드릴 수 있습니다.
25-29: 라우터 쿼리 기반 뷰 모드 전환 로직이 잘 구현되었습니다.전체화면 지도에서 돌아올 때
from=map쿼리 파라미터를 통해 지도 뷰를 유지하는 로직이 적절하게 분리되어 있습니다. 이전 리뷰에서 제안된 대로 localStorage 체크와 별도의useEffect로 분리된 점도 좋습니다.
54-70: 리스트/지도 뷰 전환 구조가 명확하고 깔끔합니다.TagGroup을 통한 뷰 모드 토글과 조건부 렌더링이 잘 구현되어 있습니다:
- 뷰 모드에 따른 적절한 높이 및 오버플로우 처리
- ResultList와 ResultMap의 명확한 분리
- 상태 관리 로직의 간결함
| @@ -0,0 +1,31 @@ | |||
| 'use client'; | |||
There was a problem hiding this comment.
Pages Router에서는 'use client' 지시어가 불필요합니다.
이 프로젝트는 Next.js App Router가 아닌 Pages Router를 사용하고 있습니다. Pages Router에서는 모든 컴포넌트가 기본적으로 클라이언트 컴포넌트로 동작하므로 'use client' 지시어를 제거해야 합니다.
다음과 같이 수정하세요:
-'use client';
-
import { cn } from '@/shared/lib';Based on learnings
📝 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.
| 'use client'; | |
| import { cn } from '@/shared/lib'; |
🤖 Prompt for AI Agents
In src/pages/map/result/components/ResultList.tsx around line 1, remove the
top-level 'use client' directive because this project uses the Next.js Pages
Router where components are already client-side; simply delete that line and
ensure no other code depends on the directive so the file compiles correctly.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/pages/map/result/index.tsx (1)
17-25: SSR 안전성 체크가 올바르게 추가되었습니다.이전 리뷰의 SSR localStorage 접근 문제가 해결되었습니다. 다만 코드 스타일 개선을 위해 작은 수정을 제안합니다.
다음과 같이 spacing을 개선하세요:
useEffect(() => { - if(typeof window === 'undefined') return; + if (typeof window === 'undefined') return; const hasSeenPopup = localStorage.getItem('seenCoursePopup');
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/pages/map/result/Map.tsx(1 hunks)src/pages/map/result/index.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/pages/map/result/Map.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-31T17:34:04.305Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.305Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/map/result/index.tsx
🧬 Code graph analysis (1)
src/pages/map/result/index.tsx (5)
src/pages/map/result/components/TagGroup.tsx (1)
TagGroup(11-34)src/shared/lib/utils.ts (1)
cn(71-73)src/pages/map/result/components/ResultList.tsx (1)
ResultList(7-31)src/pages/map/result/components/ResultMap.tsx (1)
ResultMap(6-33)src/shared/components/set/PopupSet.tsx (1)
PopupSet(11-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-deploy
🔇 Additional comments (6)
src/pages/map/result/index.tsx (6)
1-10: 임포트 구조가 깔끔합니다.필요한 의존성을 적절히 임포트하고 있으며, Next.js Pages Router 방식을 올바르게 따르고 있습니다.
27-31: useEffect 분리가 적절하게 적용되었습니다.이전 리뷰 피드백대로 query 체크와 localStorage 체크를 별도의 useEffect로 분리하여 의존성 관리가 명확해졌습니다. 쿼리 파라미터 기반 view mode 전환 로직도 정확합니다.
37-42: 인증 관련 props가 하드코딩되어 있습니다.
isLoggedIn={false}와 빈onLogin핸들러가 하드코딩되어 있습니다. 본 PR의 범위가 UI 구현에 집중되어 있다면 문제없지만, 추후 실제 인증 로직 통합이 필요한지 확인해주세요.
56-72: 뷰 전환 로직과 조건부 렌더링이 깔끔합니다.TagGroup을 통한 리스트/지도 전환 UI와 viewMode 기반 조건부 렌더링이 명확하게 구현되어 있습니다. overflow 처리도 각 뷰 모드에 맞게 적절히 설정되었습니다.
78-83: PopupSet 통합이 올바르게 완료되었습니다.이전 리뷰에서 제안된 대로
shared/components/set/PopupSet.tsx를 활용하여 팝업을 구현했습니다. 오버레이와 함께 중앙에 표시되는 구조로 UX가 개선되었습니다.
46-54: 배너 이미지 경로 확인 완료
/assets/bannerMap.svg파일이public/assets/bannerMap.svg에 정상적으로 존재합니다. 문제없습니다.
🔥 작업 내용
코스 추천 결과 페이지 (
/map/result) 신규 구현CourseResultPage생성 및 리스트 / 지도 뷰 전환 기능 추가ResultList,ResultMap,FullMap,TagGroup등 기능별 컴포넌트로 분리하여 구조화localStorage기반 팝업 노출 (seenCoursePopup) 로직 추가BottomNav,ControlBar등 공통 컴포넌트 연동 완료카카오 지도 기능 통합
useKakaoMap커스텀 훅 생성 →loadKakaoSdk유틸 함수로 SDK 로딩 분리kakao.d.ts타입 선언 추가로 전역 타입 안정성 확보MAP_LOCATIONS상수 분리로 mock 데이터 기반 위치 표시UI 및 접근성 개선
no-scrollbar), 모바일 뷰 기준 레이아웃 정리TagGroup에서 뷰 전환 버튼(지도로 보기 / 리스트로 보기) UX 개선rounded-[2rem],overflow-hidden등)🤔 추후 작업 사항
🔗 이슈
PR Point (To Reviewer)
useKakaoMap내부에서window.kakao접근 시점과loadKakaoSdk로딩 순서가 안정적인지 확인 부탁드립니다.viewMode) 흐름 및 UI 자연스러움 검토 요청드립니다.hooks/constants/components)가 적절한지 피드백 부탁드립니다.💡 UX 관점 리팩토링 포인트
📸 피그마 스크린샷 or 기능 GIF
(작업 내역 스크린샷)
-.Clipchamp.1.mp4
Summary by CodeRabbit
새로운 기능
접근성/사용성
스타일