-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEAT] 검색 결과 저장 #64
[FEAT] 검색 결과 저장 #64
Conversation
Walkthrough이 변경 사항은 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant App
participant BookmarkDetail
participant MarkerList
User->>App: 요청
App->>BookmarkDetail: 데이터 요청
BookmarkDetail->>MarkerList: 마커 데이터 요청
MarkerList->>BookmarkDetail: 마커 데이터 반환
BookmarkDetail->>User: 데이터 표시
Assessment against linked issues
Possibly related PRs
Suggested reviewers
Warning Rate limit exceeded@keemsebin has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 6 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. 📜 Recent review detailsConfiguration used: .coderabbit.yaml 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
Update: 2024년 11월 23일 23시 24분 46초 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 30
🧹 Outside diff range and nitpick comments (40)
src/utils/extractVideoId.ts (2)
1-6
: 기본 구현이 올바르지만 유지보수성 개선이 필요합니다.정규식이 복잡하여 유지보수가 어려울 수 있습니다. 여러 패턴으로 분리하여 가독성을 높이는 것을 고려해보세요.
다음과 같이 개선해보세요:
-export const extractVideoId = (url: string): string | null => { - const match = url.match( - /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?/\s]{11})/ - ); - return match ? match[1] : null; -}; +const YOUTUBE_URL_PATTERNS = { + standard: /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^"&?/\s]{11})/, + shortened: /(?:https?:\/\/)?(?:www\.)?youtu\.be\/([^"&?/\s]{11})/, + embed: /(?:https?:\/\/)?(?:www\.)?youtube\.com\/embed\/([^"&?/\s]{11})/ +}; + +export const extractVideoId = (url: string): string | null => { + for (const pattern of Object.values(YOUTUBE_URL_PATTERNS)) { + const match = url.match(pattern); + if (match) return match[1]; + } + return null; +};
1-1
: 문서화와 테스트 케이스 추가가 필요합니다.함수의 목적, 입력값, 반환값에 대한 JSDoc 문서화와 다양한 URL 형식에 대한 테스트 케이스가 필요합니다.
다음과 같이 문서화를 추가해보세요:
+/** + * YouTube URL에서 동영상 ID를 추출합니다. + * 지원하는 URL 형식: + * - https://www.youtube.com/watch?v=VIDEO_ID + * - https://youtu.be/VIDEO_ID + * - https://www.youtube.com/embed/VIDEO_ID + * + * @param url - YouTube 동영상 URL + * @returns 동영상 ID 또는 매칭되지 않을 경우 null + * + * @example + * extractVideoId('https://www.youtube.com/watch?v=dQw4w9WgXcQ') + * // Returns: 'dQw4w9WgXcQ' + */ export const extractVideoId = (url: string): string | null => {테스트 케이스 작성을 도와드릴까요?
src/hooks/common/useMarkers.tsx (1)
6-8
: 원자적 상태 관리 구현이 효율적으로 분리되었습니다.각각의 마커 관련 동작(조회, 추가, 초기화)이 개별 atom으로 분리되어 있어 관심사 분리가 잘 되어있습니다. 하지만 타입 안정성을 위해 다음과 같은 개선을 제안드립니다.
- const [markers] = useAtom(markersAtom); - const [, addMarker] = useAtom(addMarkerAtom); - const [, clearMarkers] = useAtom(clearMarkersAtom); + const [markers] = useAtom<Place[]>(markersAtom); + const [, addMarker] = useAtom<(place: Place) => void>(addMarkerAtom); + const [, clearMarkers] = useAtom<() => void>(clearMarkersAtom);src/contexts/MarkerAtom.tsx (1)
5-5
: 더 구체적인 이름을 사용하는 것이 좋을 것 같습니다.
markersAtom
이라는 이름보다searchResultMarkersAtom
과 같이 더 구체적인 이름을 사용하면 atom의 용도를 더 명확하게 표현할 수 있습니다.-export const markersAtom = atom<Place[]>([]); +export const searchResultMarkersAtom = atom<Place[]>([]);src/types/naver.ts (2)
Line range hint
8-21
: 타입 정의에 대한 문서화 제안
Place
타입의 각 필드가 어떤 상황에서 누락될 수 있는지 설명하는 JSDoc 문서를 추가하면 좋을 것 같습니다.다음과 같이 문서화를 추가해보세요:
+/** + * 장소 정보를 나타내는 타입 + * @property {number} [id] - 저장된 장소의 고유 식별자 + * @property {number} [timeStamp] - 장소가 저장된 시간 + * @property {string} title - 장소명 + * @property {string} [link] - 장소 상세 정보 링크 (네이버 검색 결과에서만 제공) + * @property {Category | string} category - 장소 카테고리 + * @property {string} [description] - 장소 설명 (네이버 검색 결과에서만 제공) + * @property {string} [telephone] - 전화번호 (제공되지 않을 수 있음) + * @property {string} address - 지번 주소 + * @property {string} [roadAddress] - 도로명 주소 (제공되지 않을 수 있음) + * @property {string} mapx - X 좌표 + * @property {string} mapy - Y 좌표 + */ export type Place = { // ... existing type definition };
Line range hint
8-21
: 타입 안전성 개선 제안좌표값(
mapx
,mapy
)의 타입이string
으로 되어있는데, 실제로는 숫자 데이터를 다루므로number
타입으로 변경하는 것이 더 적절할 것 같습니다.다음과 같이 변경을 제안드립니다:
export type Place = { // ... other properties address: string; roadAddress?: string; - mapx: string; - mapy: string; + mapx: number; + mapy: number; };src/hooks/common/useMapData.tsx (2)
6-15
: token 매개변수의 목적을 문서화해 주세요.setState 함수에서 사용되는 token 매개변수의 목적과 사용 사례를 명확히 하면 좋겠습니다.
다음과 같이 JSDoc을 추가하는 것을 제안합니다:
+/** + * 맵 데이터 상태를 관리하는 훅 + * @template T - 맵 데이터의 타입 + * @returns {Object} 맵 데이터 상태와 상태 설정 함수 + */ export const useMapData = <T,>() => { const [mapData] = useAtom<MapDataState<T>>(mapDataAtom); const [, setMapData] = useAtom(setMapDataAtom); + /** + * 맵 데이터 상태를 업데이트하는 함수 + * @param {MapDataState<T>} newState - 새로운 맵 데이터 상태 + * @param {string | null} token - [설명 필요: token의 목적] + */ const setState = useCallback(
17-17
: 반환 타입을 명시적으로 정의하면 좋겠습니다.반환 구조는 깔끔하지만, 타입 안전성을 높이기 위해 명시적인 반환 타입을 추가하면 좋겠습니다.
다음과 같은 변경을 제안합니다:
-export const useMapData = <T,>() => { +export const useMapData = <T,>(): { + state: MapDataState<T>; + setState: (newState: MapDataState<T>, token: string | null) => void; +} => {src/utils/getStorageData.ts (1)
13-16
: 세션 스토리지 접근 안전성 개선 필요브라우저의 세션 스토리지 사용 가능 여부를 확인하고 오류를 처리하는 것이 좋습니다.
다음과 같이 개선해보세요:
export const clearMapStorage = (): void => { + try { + if (typeof sessionStorage === 'undefined') { + throw new Error('Session storage is not available'); + } sessionStorage.removeItem('mapData'); sessionStorage.removeItem('bottomSheetType'); + } catch (error) { + console.error('Failed to clear map storage:', error); + } };src/hooks/api/marker/useNewMarker.ts (1)
1-3
: import 구문 순서를 수정해 주세요.외부 라이브러리와 내부 모듈의 import 순서를 다음과 같이 정리하면 좋겠습니다:
-import { useMutation } from '@tanstack/react-query'; - -import { post } from '@/lib/axios'; +import { useMutation } from '@tanstack/react-query'; +import { post } from '@/lib/axios';🧰 Tools
🪛 eslint (1.23.1)
[error] 1-1: There should be no empty line within import group
(import/order)
[error] 3-3:
@/lib/axios
import should occur before import of@tanstack/react-query
(import/order)
src/hooks/common/useFunnel.tsx (2)
12-13
: 제네릭 타입 구현이 잘 되었습니다!타입 안전성이 향상되었으며, 컴파일 타임에 더 정확한 타입 체크가 가능해졌습니다. 다만, 제네릭 타입 사용에 대한 문서화가 있으면 좋을 것 같습니다.
다음과 같은 JSDoc 문서를 추가하는 것을 제안합니다:
+/** + * Funnel 패턴을 구현하는 커스텀 훅 + * @template T - 스텝의 이름을 나타내는 문자열 리터럴 타입 + * @param initStep - 초기 스텝 값 + * @returns Funnel 컴포넌트와 관련 유틸리티 + */ export const useFunnel = <T extends string>(initStep: T) => {
20-23
: 성능 최적화가 적절하게 구현되었습니다!
useMemo
를 사용한 최적화로 불필요한 계산을 방지하고 있습니다. 의존성 배열도 정확하게 설정되어 있습니다.추가로 고려해볼 만한 사항:
- 자식 컴포넌트가 많을 경우를 대비해
Map
을 사용한 캐싱 전략을 고려해볼 수 있습니다.- 퍼포먼스 모니터링을 위한 디버그 모드 추가를 고려해보세요.
src/hooks/api/bookmarks/useBookMarkList.ts (2)
1-3
: import 구문 정리가 필요합니다.import 구문의 순서를 다음과 같이 정리하면 코드의 가독성이 향상될 것 같습니다.
-import { useQuery } from '@tanstack/react-query'; - -import { get } from '@/lib/axios'; +import { get } from '@/lib/axios'; +import { useQuery } from '@tanstack/react-query';🧰 Tools
🪛 eslint (1.23.1)
[error] 1-1: There should be no empty line within import group
(import/order)
[error] 3-3:
@/lib/axios
import should occur before import of@tanstack/react-query
(import/order)
13-20
: 타입에 대한 문서화가 필요합니다.각 필드의 의미와 용도를 명확히 하기 위해 JSDoc 주석을 추가하면 좋을 것 같습니다.
+/** + * 북마크 상세 정보를 나타내는 타입 + */ export type BookmarkDetail = { + /** 북마크의 고유 식별자 */ bookmarkId: number; + /** 유튜버의 프로필 이미지 URL */ youtuberProfile: string; + /** 유튜버 이름 */ name: string; + /** 저장된 마커의 수 */ markersCount: number; + /** 북마크 유형 */ bookmarkType: string; + /** 유튜브 동영상 링크 */ youtubeLink: string; };src/contexts/MapAtom.tsx (1)
4-9
: 타입 정의에 문서화가 필요합니다타입 정의가 명확하지만, 각 타입의 용도와 필드에 대한 설명이 있으면 더 좋을 것 같습니다.
다음과 같이 JSDoc 주석을 추가하는 것을 제안합니다:
+/** + * 바텀 시트의 표시 상태를 나타내는 타입 + * - extract: 장소 추출 모드 + * - search: 검색 모드 + * - list: 목록 보기 모드 + */ export type BottomSheetType = 'extract' | 'search' | 'list'; +/** + * 지도 데이터의 상태를 관리하는 제네릭 타입 + * @template T - 저장할 데이터의 타입 + */ export type MapDataState<T> = { + /** 저장된 데이터 (없을 경우 null) */ data: T | null; + /** 현재 바텀 시트의 표시 상태 (없을 경우 null) */ type: BottomSheetType | null; };src/hooks/common/useInput.tsx (1)
Line range hint
12-15
: 입력값 검증 시점 최적화 제안
onBlur
이벤트에서만 유효성을 검사하면 사용자가 포커스를 잃기 전까지 피드백을 받을 수 없습니다.실시간 검증을 위해
onChange
이벤트에서도 유효성 검사를 수행하는 것을 고려해보세요:const onChange: ChangeEventHandler<HTMLInputElement> = useCallback((e) => { const value = e.target.value; setState(value); + if (value.length > 0) { + setIsValid(validation.some((criteria) => value.includes(criteria))); + } }, []);src/constants/funnelStep.ts (1)
28-28
: 타입에 대한 문서화를 추가하면 좋을 것 같습니다.타입의 용도와 각 플로우의 의미를 설명하는 JSDoc 주석을 추가하는 것을 제안드립니다.
+/** 페이지 플로우의 종류를 정의합니다. + * - search: 검색 결과에서 시작하는 플로우 + * - extract: 추출된 장소에서 시작하는 플로우 + * - list: 리스트에서 시작하는 플로우 + */ export type FlowType = keyof typeof FLOW_CONFIGS;src/hooks/common/useSessionDataLoader.tsx (2)
8-10
: token 매개변수의 타입 안전성을 개선하면 좋겠습니다.
token
매개변수의 타입을 더 구체적으로 정의하면 좋을 것 같습니다.다음과 같이 변경을 제안드립니다:
-export const useSessionDataLoader = (token: string | null) => { +type AuthToken = string; +export const useSessionDataLoader = (token: AuthToken | null) => {
25-28
: useEffect에 정리(cleanup) 함수 추가를 고려해보세요.컴포넌트 언마운트 시 필요한 정리 작업이 있을 수 있으며, 로딩 상태 관리도 추가하면 좋을 것 같습니다.
다음과 같은 개선을 제안드립니다:
+const [isLoading, setIsLoading] = useState(false); + useEffect(() => { - loadSessionStorageData(); + let mounted = true; + + const loadData = async () => { + if (!mounted) return; + setIsLoading(true); + await loadSessionStorageData(); + if (mounted) { + setIsLoading(false); + } + }; + + loadData(); + + return () => { + mounted = false; + }; }, [loadSessionStorageData]);src/hooks/api/marker/useMarkerList.ts (3)
1-4
: import 문 순서를 수정해 주세요import 문의 순서가 올바르지 않습니다. 다음과 같이 수정하는 것이 좋습니다:
-import { useQuery } from '@tanstack/react-query'; - -import { get } from '@/lib/axios'; - +import { get } from '@/lib/axios'; +import { useQuery } from '@tanstack/react-query';🧰 Tools
🪛 eslint (1.23.1)
[error] 1-1: There should be no empty line within import group
(import/order)
[error] 3-3:
@/lib/axios
import should occur before import of@tanstack/react-query
(import/order)
5-28
: 타입 정의에 대한 문서화 및 개선 제안타입 정의가 잘 되어 있지만, 다음 사항들을 고려해 보시기 바랍니다:
- 각 타입에 대한 JSDoc 문서화 추가
mapx
와mapy
를 문자열 대신 숫자로 처리하는 것이 좋을 것 같습니다예시:
+/** + * 북마크와 관련된 마커 정보를 담는 타입 + */ export type Marker = { bookmarkName: string; markers: MarkersList; }; +/** + * 마커 목록과 페이지네이션 정보를 담는 타입 + */ export type MarkersList = { data: MarkerDetail[]; currentCursor: number; pageSize: number; hasNext: boolean; nextCursor: number; }; +/** + * 개별 마커의 상세 정보를 담는 타입 + */ export type MarkerDetail = { markerId: number; title: string; address: string; - mapx: string; - mapy: string; + mapx: number; + mapy: number; category: { majorCategory: string; middleCategory: string; }; };
34-34
: API 엔드포인트 구조 개선 제안현재 API 엔드포인트가 상대 경로로 되어 있습니다. 다음과 같은 개선을 고려해 보세요:
- API 엔드포인트를 상수로 분리
- 기본 URL을 환경 변수로 관리
예시:
// constants/api.ts export const API_ENDPOINTS = { MARKERS: (bookmarkId: number) => `/api/markers/${bookmarkId}`, } as const; // hooks/api/marker/useMarkerList.ts import { API_ENDPOINTS } from '@/constants/api'; // ... 사용시 get<Marker>(API_ENDPOINTS.MARKERS(bookmarkId), ...)src/lib/axios.ts (2)
11-11
: 제네릭 타입 제약 조건 추가 권장현재 제네릭 타입
T
에 대한 제약 조건이 없어 잠재적인 타입 관련 문제가 발생할 수 있습니다.다음과 같이 제네릭 타입에 제약 조건을 추가하는 것을 권장합니다:
-export const get = async <T>(...args: Parameters<typeof instance.get>) => { +export const get = async <T extends Record<string, unknown>>(...args: Parameters<typeof instance.get>) => {다른 메서드들에도 동일하게 적용해주세요.
Also applies to: 16-16, 21-21, 26-26, 31-31
12-13
: 응답 처리 유연성 개선 제안현재 구현은
response.data
만 반환하고 있어, 상태 코드나 헤더와 같은 중요한 메타데이터에 접근할 수 없습니다.다음과 같이 전체 응답을 선택적으로 받을 수 있도록 개선하는 것을 제안합니다:
export const get = async <T extends Record<string, unknown>>( + fullResponse = false, ...args: Parameters<typeof instance.get> ) => { const response = await instance.get<T>(...args); - return response.data; + return fullResponse ? response : response.data; };이렇게 하면 필요한 경우 전체 응답 객체에 접근할 수 있습니다.
Also applies to: 17-18, 22-23, 27-28, 32-33
src/components/features/LinkForm/LinkForm.stories.tsx (1)
Line range hint
21-45
: 스토리에 엣지 케이스 추가를 고려해보세요현재 기본적인 플로우만 테스트하고 있습니다. 다음과 같은 엣지 케이스들을 추가하면 좋을 것 같습니다:
- 잘못된 URL 입력 시나리오
- 로딩 상태에서의 UI
- 에러 상태에서의 UI
예시 코드:
export const WithInvalidUrl: Story = { render: () => { const { Funnel, Step, setStep } = useFunnel<'입력전' | '링크입력' | '추출상태'>('링크입력'); return ( <Layout> <Funnel> <Step name="링크입력"> <LinkInput onNext={() => setStep('추출상태')} onHomeClick={() => {}} context={{ state: 'invalid-url', setState: () => {} }} /> </Step> </Funnel> </Layout> ); }, };src/components/features/BookmarkList/BookmarkList.tsx (2)
23-27
: 접근성 개선이 필요합니다클릭 가능한 요소들에 대한 키보드 접근성과 ARIA 속성이 누락되었습니다.
다음과 같이 수정을 제안합니다:
<div key={item.bookmarkId} onClick={() => handleBookmarkClick(item.bookmarkId)} + onKeyDown={(e) => e.key === 'Enter' && handleBookmarkClick(item.bookmarkId)} + role="button" + tabIndex={0} + aria-label={`${item.name}의 북마크 상세보기`} className="flex flex-row justify-between items-center cursor-pointer" >그리고 위치 아이콘에도 적절한 aria-label을 추가해주세요:
- <Icon name="location" size={15} /> + <Icon name="location" size={15} aria-label="저장된 위치 수" />Also applies to: 40-43
29-37
: 이미지 최적화가 필요합니다유튜버 프로필 이미지에 대한 로딩 및 오류 처리가 누락되었습니다.
다음과 같이 수정을 제안합니다:
{item.youtuberProfile ? ( <img src={item.youtuberProfile} className="w-12 h-12 rounded-full" alt={`${item.name}의 프로필 이미지`} + loading="lazy" + onError={(e) => { + e.currentTarget.onerror = null; + e.currentTarget.src = '/default-profile.png'; + }} /> ) : ( <Icon name="findy1" className="w-11 h-11" /> )}src/components/features/BookmarkDetail/BookmarkDetail.tsx (2)
12-12
: Props 타입에 대한 문서화 개선이 필요합니다.Props 인터페이스가 간단하지만, 각 속성의 목적과 사용법에 대한 설명이 부족합니다.
다음과 같이 개선해보세요:
-type Props = { bookmarkId: number; onPrev: () => void }; +/** BookmarkDetail 컴포넌트 Props */ +type BookmarkDetailProps = { + /** 북마크의 고유 식별자 */ + bookmarkId: number; + /** 이전 화면으로 돌아가기 위한 콜백 함수 */ + onPrev: () => void; +};
13-64
: 컴포넌트 아키텍처 개선이 필요합니다.현재 컴포넌트가 데이터 페칭과 표현 로직을 모두 처리하고 있어 단일 책임 원칙을 위반하고 있습니다.
다음과 같은 구조 개선을 제안드립니다:
- 컨테이너 컴포넌트와 프레젠테이션 컴포넌트로 분리:
// BookmarkDetailContainer.tsx export const BookmarkDetailContainer = ({ bookmarkId, onPrev }: Props) => { const { token } = useAuth(); const { data, isError, isLoading } = useMarkerList(bookmarkId, token); const [, setMarkers] = useAtom(markersAtom); // ... 데이터 처리 로직 return <BookmarkDetailPresentation bookmarkName={data?.bookmarkName} markers={data?.markers?.data} onBack={() => { setMarkers([]); onPrev(); }} />; }; // BookmarkDetailPresentation.tsx export const BookmarkDetailPresentation = ({ bookmarkName, markers, onBack }: PresentationProps) => { // ... UI 렌더링 로직 };이렇게 분리하면 다음과 같은 이점이 있습니다:
- 테스트 용이성 향상
- 컴포넌트 재사용성 증가
- 관심사의 명확한 분리
src/components/features/SearchResultsList/SearchResultsList.tsx (3)
14-18
: Props 타입 정의를 개선하세요.Props 인터페이스에 다음 사항들을 추가하면 좋을 것 같습니다:
- JSDoc 문서화
- 로딩 상태 관리를 위한
isLoading
prop- 에러 상태 관리를 위한
error
prop+/** + * 검색 결과 목록 컴포넌트의 Props + * @param places - 장소 목록 + * @param onNext - 다음 단계로 이동하는 콜백 + * @param onSelect - 장소 선택 시 호출되는 콜백 + */ type Props = { places: Place[]; + isLoading?: boolean; + error?: Error; onNext: () => void; onSelect: (place: NewMarker) => void; };
19-23
: 상태 관리 로직을 개선하세요.선택 로직을 별도의 커스텀 훅으로 분리하면 코드의 재사용성과 테스트 용이성이 향상될 것 같습니다.
// useSelection.ts export const useSelection = () => { const [selectedIndex, setSelectedIndex] = useState<number | null>(null); const handleSelect = (index: number) => { const newIndex = selectedIndex === index ? null : index; setSelectedIndex(newIndex); }; return { selectedIndex, handleSelect }; };
1-71
: 아키텍처 개선 사항을 고려하세요.컴포넌트의 안정성과 테스트 용이성을 위해 다음 사항들을 고려해보세요:
- ErrorBoundary 추가
- 단위 테스트 작성
- E2E 테스트 시나리오 작성
특히 다음과 같은 테스트 케이스들이 필요합니다:
- 장소 선택/해제
- 로그인 모달 표시
- 에러 상태 처리
- 키보드 네비게이션
src/components/features/LinkForm/ExtractionStatus.tsx (1)
24-25
: 썸네일 URL 생성 로직 개선이 필요합니다.현재 구현은 이전보다 개선되었지만, 다음과 같은 추가 개선사항을 고려해보시기 바랍니다:
- 썸네일 URL을 상수나 유틸리티 함수로 분리
- maxresdefault.jpg가 존재하지 않을 경우를 대비한 폴백(fallback) 옵션 추가
다음과 같이 개선해보시는 건 어떨까요?
- const thumbnailUrl = videoId ? `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg` : ''; + const thumbnailUrl = videoId ? getThumbnailUrl(videoId) : ''; // utils/youtube.ts에 추가 +export const getThumbnailUrl = (videoId: string) => { + const qualities = ['maxresdefault', 'hqdefault', 'mqdefault', 'default']; + return `https://img.youtube.com/vi/${videoId}/${qualities[0]}.jpg`; +};src/components/features/SearchResultsList/BookmarkSelectionList.tsx (1)
12-15
: Props 타입 정의를 더 구체적으로 개선하세요.
onNext
콜백의 용도가 북마크 저장 완료 후 다음 단계로 진행하는 것이므로, 이를 타입에 더 명확하게 표현하면 좋겠습니다.type Props = { - onNext: () => void; + onNext: () => Promise<void> | void; selectedPlace: NewMarker; };src/components/features/ExtractedPlacesList/ExtractedPlacesList.tsx (3)
38-41
: map 함수에서 unused variable 경고를 제거하는 더 나은 방법이 있습니다.eslint 주석을 사용하는 대신 구조 분해 할당을 활용하여 코드를 개선할 수 있습니다.
- // eslint-disable-next-line @typescript-eslint/no-unused-vars - .map(({ id, ...placeData }) => placeData), + .map(({ id: _, ...placeData }) => placeData),
Line range hint
71-96
: key prop 누락 및 최적화 이슈가 있습니다.
- Fragment(
<>
)에 key prop이 없고, div에 잘못 배치되어 있습니다.- 반복적인 스타일 로직은 별도의 스타일 컴포넌트로 추출하면 좋습니다.
- <> + <React.Fragment key={`${item.title}-${item.address}`}> <div - key={`${item.title}-${item.address}`} className={`flex flex-row justify-between items-center ${ index !== places.places.length - 1 && 'pb-2' }`} >
Line range hint
56-57
: TODO 코멘트 처리가 필요합니다.이미지 대체와 컴포넌트화에 대한 TODO 항목이 있습니다. 이를 해결하기 위한 구체적인 계획이 필요해 보입니다.
다음 작업들을 도와드릴 수 있습니다:
- 이미지 폴백(fallback) 컴포넌트 구현
- 반복되는 리스트 아이템을 별도 컴포넌트로 추출
필요하시다면 GitHub 이슈를 생성해드리겠습니다.
src/pages/MapView.tsx (1)
105-111
: BottomSheet 컴포넌트의 상태 관리 개선이 필요합니다.현재 구현은 다소 복잡하며, 상태 업데이트 로직이 인라인으로 작성되어 있습니다. 이를 별도의 훅이나 함수로 분리하면 유지보수성이 향상될 것 같습니다.
다음과 같은 리팩토링을 제안드립니다:
const useBottomSheetState = (token: string) => { const handleClose = useCallback(() => { setState({ ...state, type: null }, token); }, [state, token]); return { isOpen: !!state.type, handleClose }; };그리고 컴포넌트에서는 다음과 같이 사용:
- <BottomSheet - isOpen={!!state.type} - setIsOpen={(open) => { - if (!open) setState({ ...state, type: null }, token); - }} - > + const { isOpen, handleClose } = useBottomSheetState(token); + <BottomSheet + isOpen={isOpen} + setIsOpen={(open) => !open && handleClose()} + >src/hooks/common/useBottomFunnel.tsx (2)
36-42
: 비로그인 상태에 대한 유형별 처리 고려 필요현재
!token && type === 'list'
조건으로 렌더링을 제어하고 있습니다. 만약 다른type
에서도 비로그인 상태에 대한 처리가 필요하다면 조건문을 수정하여 모든 경우를 포괄할 수 있도록 고려해주세요.
29-34
:useCallback
의 의존성 배열 확인 필요
handleStepChange
함수의 의존성 배열에setStep
만 포함되어 있습니다. 하지만handleStepChange
내부에서 사용하는nextStep
은 함수의 매개변수이므로 의존성 배열에 포함할 필요는 없습니다. 현재 상태로도 문제가 없으나, 의존성 배열을 재확인해주세요.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
⛔ Files ignored due to path filters (3)
.yarn/cache/jotai-npm-2.10.3-20aff9aa6c-64f6536aaa.zip
is excluded by!**/.yarn/**
,!**/*.zip
.yarn/install-state.gz
is excluded by!**/.yarn/**
,!**/*.gz
yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (44)
.pnp.cjs
(3 hunks)package.json
(1 hunks)src/App.tsx
(1 hunks)src/components/features/BookmarkDetail/BookmarkDetail.tsx
(1 hunks)src/components/features/BookmarkDetail/index.tsx
(1 hunks)src/components/features/BookmarkList/BookmarkList.tsx
(1 hunks)src/components/features/BookmarkList/index.tsx
(1 hunks)src/components/features/BottomSheetContent/BottomSheetContent.tsx
(1 hunks)src/components/features/BottomSheetContent/ExtractedPlaces.tsx
(0 hunks)src/components/features/BottomSheetContent/SearchResult.tsx
(0 hunks)src/components/features/BottomSheetContent/index.tsx
(0 hunks)src/components/features/ExtractedPlacesList/ExtractedPlacesList.tsx
(3 hunks)src/components/features/ExtractedPlacesList/index.tsx
(1 hunks)src/components/features/LinkForm/ExtractionStatus.tsx
(2 hunks)src/components/features/LinkForm/Landing.tsx
(1 hunks)src/components/features/LinkForm/LinkForm.stories.tsx
(1 hunks)src/components/features/LinkForm/LinkInput.tsx
(1 hunks)src/components/features/SearchList/SearchList.tsx
(0 hunks)src/components/features/SearchResultsList/BookmarkSelectionList.tsx
(1 hunks)src/components/features/SearchResultsList/SearchResultsList.tsx
(1 hunks)src/components/features/SearchResultsList/index.tsx
(1 hunks)src/constants/funnelStep.ts
(1 hunks)src/contexts/MapAtom.tsx
(1 hunks)src/contexts/MapContext.tsx
(0 hunks)src/contexts/MarkerAtom.tsx
(1 hunks)src/contexts/MarkerContext.tsx
(0 hunks)src/hooks/api/bookmarks/useBookMarkList.ts
(1 hunks)src/hooks/api/link/useYoutubePlace.ts
(1 hunks)src/hooks/api/marker/useMarkerList.ts
(1 hunks)src/hooks/api/marker/useNewMarker.ts
(1 hunks)src/hooks/common/useBottomFunnel.tsx
(1 hunks)src/hooks/common/useFunnel.tsx
(2 hunks)src/hooks/common/useInput.tsx
(1 hunks)src/hooks/common/useMapData.tsx
(1 hunks)src/hooks/common/useMapStorage.tsx
(0 hunks)src/hooks/common/useMarkers.tsx
(1 hunks)src/hooks/common/useSessionDataLoader.tsx
(1 hunks)src/lib/axios.ts
(1 hunks)src/lib/external.ts
(1 hunks)src/pages/Link.tsx
(1 hunks)src/pages/MapView.tsx
(5 hunks)src/types/naver.ts
(1 hunks)src/utils/extractVideoId.ts
(1 hunks)src/utils/getStorageData.ts
(1 hunks)
💤 Files with no reviewable changes (7)
- src/components/features/BottomSheetContent/ExtractedPlaces.tsx
- src/components/features/BottomSheetContent/SearchResult.tsx
- src/components/features/BottomSheetContent/index.tsx
- src/components/features/SearchList/SearchList.tsx
- src/contexts/MapContext.tsx
- src/contexts/MarkerContext.tsx
- src/hooks/common/useMapStorage.tsx
✅ Files skipped from review due to trivial changes (6)
- src/components/features/BookmarkDetail/index.tsx
- src/components/features/BookmarkList/index.tsx
- src/components/features/ExtractedPlacesList/index.tsx
- src/components/features/LinkForm/Landing.tsx
- src/components/features/LinkForm/LinkInput.tsx
- src/components/features/SearchResultsList/index.tsx
🧰 Additional context used
🪛 eslint (1.23.1)
src/hooks/api/bookmarks/useBookMarkList.ts
[error] 1-1: There should be no empty line within import group
(import/order)
[error] 3-3: @/lib/axios
import should occur before import of @tanstack/react-query
(import/order)
src/hooks/api/marker/useMarkerList.ts
[error] 1-1: There should be no empty line within import group
(import/order)
[error] 3-3: @/lib/axios
import should occur before import of @tanstack/react-query
(import/order)
src/hooks/api/marker/useNewMarker.ts
[error] 1-1: There should be no empty line within import group
(import/order)
[error] 3-3: @/lib/axios
import should occur before import of @tanstack/react-query
(import/order)
🔇 Additional comments (33)
.pnp.cjs (2)
74-74
: 의존성 추가가 올바르게 구성되었습니다.
Jotai 라이브러리가 Yarn PnP 구성에 정상적으로 추가되었습니다.
Also applies to: 8165-8165
10108-10129
: Peer 의존성 버전 호환성을 확인하세요.
Jotai v2.10.3이 현재 프로젝트에서 사용 중인 React 버전(18.3.1)과 @types/react 버전(18.3.10)과 호환되는지 확인이 필요합니다.
✅ Verification successful
Jotai v2.10.3의 peer 의존성이 현재 프로젝트의 React 버전과 호환됩니다.
Jotai v2.10.3은 React >=17.0.0 및 @types/react >=17.0.0을 요구하며, 현재 프로젝트에서 사용 중인 React ^18.3.1과 @types/react ^18.3.10은 이 요구사항을 충족합니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify jotai's peer dependencies compatibility with current React versions
# Check jotai's peer dependencies requirements
curl -s https://registry.npmjs.org/jotai/2.10.3 | jq '.peerDependencies'
# Check current React version compatibility range
curl -s https://registry.npmjs.org/jotai/2.10.3 | jq '.peerDependencies."react"'
Length of output: 222
Script:
#!/bin/bash
# Check package.json for React and @types/react versions
fd package.json --type f --exec cat {} \; | jq '.dependencies["react"], .dependencies["@types/react"], .devDependencies["react"], .devDependencies["@types/react"]'
Length of output: 199
src/App.tsx (2)
9-11
: 컴포넌트 구조가 개선되었습니다!
컨텍스트 프로바이더를 제거하고 구조를 단순화한 것이 좋은 변경사항입니다. 이는 불필요한 리렌더링을 방지하는데 도움이 될 것입니다.
9-11
: 컨텍스트에서 Jotai로의 마이그레이션 검증이 필요합니다.
이전에 MapDataProvider
와 MarkerProvider
를 사용하던 컴포넌트들이 Jotai로 올바르게 마이그레이션되었는지 확인이 필요합니다.
다음 스크립트를 실행하여 컨텍스트 사용 여부를 확인해주세요:
✅ Verification successful
Let me check the content of the Jotai-related files to ensure proper migration.
Jotai로의 마이그레이션이 성공적으로 완료되었습니다.
코드베이스 검사 결과, 기존 Context API (MapDataProvider
, MarkerProvider
)의 사용이 완전히 제거되었으며, Jotai 기반의 상태 관리로 성공적으로 전환되었음을 확인했습니다:
MapAtom.tsx
와MarkerAtom.tsx
에서 적절한 atom 정의useMapData
와useMarkers
훅에서 Jotai의useAtom
사용- 컴포넌트(
BookmarkDetail
)에서 Jotai atom 올바르게 사용
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify if any components are still using the old context
# Check for MapDataContext usage
echo "Checking for MapDataContext usage..."
rg -l "useContext.*MapDataContext|MapDataProvider"
# Check for MarkerContext usage
echo "Checking for MarkerContext usage..."
rg -l "useContext.*MarkerContext|MarkerProvider"
# Check for new Jotai atom usage
echo "Checking for Jotai atom usage..."
rg -l "useAtom|atom"
Length of output: 550
Script:
#!/bin/bash
# Check the implementation of Jotai atoms and their usage
echo "=== MapAtom.tsx ==="
cat src/contexts/MapAtom.tsx
echo -e "\n=== MarkerAtom.tsx ==="
cat src/contexts/MarkerAtom.tsx
echo -e "\n=== useMapData.tsx ==="
cat src/hooks/common/useMapData.tsx
echo -e "\n=== useMarkers.tsx ==="
cat src/hooks/common/useMarkers.tsx
echo -e "\n=== BookmarkDetail.tsx ==="
cat src/components/features/BookmarkDetail/BookmarkDetail.tsx
Length of output: 4713
src/hooks/common/useMarkers.tsx (2)
1-1
: 상태 관리 라이브러리 마이그레이션이 잘 이루어졌습니다.
Context API에서 Jotai로의 전환이 깔끔하게 이루어졌습니다. 이는 PR 목표에서 언급된 불필요한 리렌더링 문제를 해결하는데 도움이 될 것입니다.
Also applies to: 3-3
10-14
: 훅의 반환값이 명확하게 구조화되었습니다.
객체 형태로 반환하여 사용하는 쪽에서 필요한 기능만 구조 분해 할당으로 가져갈 수 있어 좋습니다.
이 훅의 사용처를 확인하기 위해 다음 스크립트를 실행합니다:
✅ Verification successful
useMarkers 훅이 올바르게 사용되고 있음을 확인했습니다.
검색 결과를 통해 다음과 같은 사용 패턴을 확인했습니다:
- MapView에서
addMarker
,clearMarkers
구조 분해 할당 사용 - useSessionDataLoader에서
clearMarkers
,addMarker
구조 분해 할당 사용 - ExtractedPlacesList에서
clearMarkers
구조 분해 할당 사용 - BookmarkSelectionList에서
clearMarkers
구조 분해 할당 사용 - NaverMap에서
markers
구조 분해 할당 사용
모든 사용처에서 필요한 기능만 구조 분해 할당으로 가져가서 사용하고 있어 훅의 반환값이 효율적으로 활용되고 있습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 훅의 사용처를 검색하여 올바르게 사용되고 있는지 확인합니다.
echo "useMarkers 훅의 사용처 확인:"
rg "useMarkers\(\)" -A 5
Length of output: 2750
src/contexts/MarkerAtom.tsx (3)
1-4
: LGTM! 임포트가 깔끔하게 정리되어 있습니다.
필요한 의존성만 정확하게 임포트되어 있습니다.
12-14
: 마커 초기화 전에 사용자 확인이 필요할 수 있습니다.
모든 마커를 한 번에 삭제하는 것은 위험할 수 있으므로, 컴포넌트 레벨에서 사용자 확인을 추가하는 것이 좋습니다.
1-14
: 검색 결과 저장 API와의 연동 확인이 필요합니다.
마커 상태 관리 구현이 잘 되어있지만, PR 목적인 검색 결과 저장 기능과의 연동이 필요합니다. API 호출 시점과 마커 상태 업데이트 순서를 확인해주세요.
✅ Verification successful
검색 결과 저장 기능이 올바르게 구현되어 있습니다.
코드베이스 분석 결과, 마커 상태 관리와 검색 결과 저장이 다음과 같이 적절하게 연동되어 있습니다:
useBottomFunnel
훅에서 검색 결과(Place[]
)를 받아 단계별 저장 플로우를 관리합니다useNewMarker
훅을 통해 선택된 장소를 북마크에 저장하는 API 호출이 구현되어 있습니다- 마커 상태(
MarkerAtom
)는 UI 표시 용도로만 사용되며, 실제 데이터 저장은 API를 통해 이루어집니다
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 검색 결과 저장 API 사용 확인
# API 호출 관련 코드 검색
rg -t typescript -t javascript "saveSearchResult|searchSave" -A 5
Length of output: 102
Script:
#!/bin/bash
# Search for API related code with common HTTP method patterns
rg -g '*.{ts,tsx,js,jsx}' '(post|put|patch|get)\s*\([^)]*search' -A 5
# Search for any search-related API calls
rg -g '*.{ts,tsx,js,jsx}' 'api.*search' -A 5
# Look for components or hooks that might handle search result saving
rg -g '*.{ts,tsx,js,jsx}' '(save|store).*search' -A 5
# Check for any Place type usage which might indicate search result handling
rg -g '*.{ts,tsx,js,jsx}' 'Place.*\[\]' -A 5
Length of output: 5576
Script:
#!/bin/bash
# Check for any API calls or mutations in hooks directory
rg -g '*.{ts,tsx}' 'useMutation|useQuery' ./src/hooks -A 5
# Look for any save-related hooks
rg -g '*.{ts,tsx}' 'use.*Save' ./src/hooks -A 5
# Check bottom funnel implementation for save functionality
ast-grep --pattern 'const useBottomFunnel = ({ type, data }: bottomFunnelProps) => {
$$$
}'
Length of output: 9979
src/types/naver.ts (1)
13-13
: 선택적 필드 처리 검증이 필요합니다
해당 필드들을 선택적으로 변경하면서 관련 컴포넌트에서 적절한 처리가 필요할 것 같습니다. 특히 BookmarkDetail
과 SearchResultsList
컴포넌트에서 이 필드들의 부재를 고려한 처리가 되어있는지 확인이 필요합니다.
Also applies to: 15-15, 16-16, 18-18
✅ Verification successful
선택적 필드 처리가 안전하게 구현되어 있습니다
컴포넌트 코드를 검토한 결과, link
, description
, telephone
, roadAddress
필드들이 직접적으로 사용되지 않고 있어 선택적 필드로 변경하는 것이 안전합니다. BookmarkDetail
과 SearchResultsList
컴포넌트는 필수 필드인 title
, address
, category
만 사용하고 있습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 선택적 필드를 사용하는 컴포넌트들의 처리 방식 확인
# Place 타입의 선택적 필드를 사용하는 패턴 검색
rg -t tsx -t ts "(?:link|description|telephone|roadAddress)" src/
Length of output: 95
Script:
#!/bin/bash
# Description: 선택적 필드를 사용하는 컴포넌트들의 처리 방식 확인
# Place 타입의 선택적 필드 사용 패턴 검색 (확장자 수정)
rg "(?:link|description|telephone|roadAddress)" --type ts --type typescript src/
# BookmarkDetail과 SearchResultsList 컴포넌트 검색
fd "BookmarkDetail|SearchResultsList" --type f --extension ts --extension tsx src/
Length of output: 324
Script:
#!/bin/bash
# BookmarkDetail과 SearchResultsList 컴포넌트의 내용 확인
cat src/components/features/BookmarkDetail/BookmarkDetail.tsx
cat src/components/features/SearchResultsList/SearchResultsList.tsx
# Place 타입 사용 패턴 재검색
rg "(?:link|description|telephone|roadAddress)" src/components/features/BookmarkDetail/BookmarkDetail.tsx src/components/features/SearchResultsList/SearchResultsList.tsx
Length of output: 4920
src/hooks/common/useMapData.tsx (2)
1-4
: 의존성 가져오기가 깔끔하게 구성되었습니다.
React Context에서 Jotai로의 전환이 잘 반영되어 있으며, 필요한 의존성들이 명확하게 정의되어 있습니다.
1-17
: Context에서 Jotai로의 전환이 적절히 이루어졌는지 확인이 필요합니다.
이 변경으로 인한 성능 개선을 확인하기 위해 불필요한 리렌더링이 실제로 감소했는지 검증이 필요합니다.
✅ Verification successful
Context에서 Jotai로의 전환이 성공적으로 완료되었습니다.
코드베이스 분석 결과:
- Context 관련 코드가 완전히 제거되었으며, 새로운 Jotai 기반 구현이 적절히 이루어졌습니다.
MapAtom.tsx
에서 atom이 올바르게 정의되어 있으며, sessionStorage 연동도 잘 구현되어 있습니다.useMapData
훅이MapView
와useSessionDataLoader
에서 정상적으로 사용되고 있습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 이전 Context 구현과 관련된 파일들이 모두 제거되었는지 확인
# Context 관련 파일 검색
echo "Searching for old context files..."
rg -l "MapContext|MapProvider"
# Context 사용 여부 확인
echo "Checking for remaining useContext usage..."
rg "useContext.*Map"
Length of output: 229
Script:
#!/bin/bash
# Let's verify the Jotai implementation and its usage
# Check the MapAtom implementation
echo "Checking MapAtom implementation..."
rg -A 10 "mapDataAtom" src/
# Check all files using the MapAtom
echo "Checking MapAtom usage..."
rg "useMapData|mapDataAtom"
# Check if there are any remaining Context-like implementations
echo "Checking for any remaining Context implementations..."
fd -e tsx -e ts -e jsx -e js --exec grep -l "createContext.*Map"
Length of output: 3029
src/hooks/api/link/useYoutubePlace.ts (2)
17-17
: URL 인코딩 추가로 보안성이 향상되었습니다.
encodeURIComponent
를 사용하여 URL 파라미터를 적절히 인코딩하는 것은 매우 중요한 보안 개선사항입니다. 특수 문자나 한글이 포함된 YouTube 링크를 안전하게 처리할 수 있게 되었습니다.
19-19
: 재시도 로직 추가로 안정성이 개선되었습니다.
일시적인 네트워크 오류에 대한 복원력을 제공하기 위해 단일 재시도를 추가한 것은 적절한 선택입니다. 사용자 경험과 서버 부하 사이의 균형이 잘 맞춰져 있습니다.
src/utils/getStorageData.ts (1)
1-1
: 파일 구조의 일관성 검토 필요
jotai
로 마이그레이션했다는 PR 목표에 따르면, MapAtom
은 contexts
폴더가 아닌 다른 위치(예: atoms
또는 store
)에 있어야 할 것 같습니다.
다음 스크립트로 파일 위치를 확인해보겠습니다:
src/hooks/api/bookmarks/useBookMarkList.ts (1)
5-11
: 타입 정의가 명확합니다.
페이지네이션 관련 필드들이 잘 정의되어 있으며, 커서 기반 페이지네이션 구현에 적합합니다.
src/constants/funnelStep.ts (3)
3-9
: 단계 상수가 명확하게 정의되어 있습니다!
상수명과 값이 직관적이며, as const
사용으로 타입 안정성이 보장됩니다.
11-11
: 타입 정의가 적절합니다!
STEPS
객체의 값들을 유니온 타입으로 잘 추출하였습니다.
13-26
: 플로우 설정의 단계 순서 검증이 필요합니다.
각 플로우의 단계 배열이 올바른 순서로 정의되어 있는지 확인이 필요합니다.
✅ Verification successful
플로우 설정의 단계 순서가 적절히 구현되어 있습니다.
코드베이스 분석 결과:
FLOW_CONFIGS
의 단계 순서가 논리적으로 올바르게 정의되어 있습니다useBottomFunnel
훅에서handleStepChange
함수를 통해 단계 전환이 적절히 관리되고 있습니다- 각 플로우 타입별로 필요한 단계들이 순차적으로 구성되어 있습니다
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the usage of flow steps in components and hooks
# Search for components using these steps
echo "컴포넌트에서 단계 사용 확인:"
rg -l "STEPS\.(SEARCH_PLACES|EXTRACTED_PLACES|LIST_SELECT|LIST|BOOKMARK_DETAIL)" --type ts --type tsx
# Search for step order validation logic
echo "단계 순서 검증 로직 확인:"
rg "steps\[.*\]|steps\.indexOf" --type ts --type tsx
Length of output: 298
Script:
#!/bin/bash
# Search for components using these steps without file type restrictions
echo "컴포넌트에서 단계 사용 확인:"
rg -l "STEPS\.(SEARCH_PLACES|EXTRACTED_PLACES|LIST_SELECT|LIST|BOOKMARK_DETAIL)"
# Search for step order validation logic
echo "단계 순서 검증 로직 확인:"
rg "steps\[.*\]|steps\.indexOf"
# Search for flow navigation logic
echo "플로우 네비게이션 로직 확인:"
rg "FLOW_CONFIGS" -A 5
Length of output: 1709
Script:
#!/bin/bash
# Check the implementation of useFunnel hook
echo "useFunnel 훅 구현 확인:"
rg -A 10 "export .* useFunnel" src/hooks/common/useFunnel.ts
# Check step transitions in useBottomFunnel
echo "useBottomFunnel 단계 전환 로직 확인:"
rg -A 10 "setStep" src/hooks/common/useBottomFunnel.tsx
# Check step type definitions
echo "단계 타입 정의 확인:"
cat src/constants/funnelStep.ts
Length of output: 1716
src/hooks/common/useSessionDataLoader.tsx (1)
1-6
: 의존성 import가 깔끔하게 구성되어 있습니다!
필요한 React 훅들과 유틸리티 함수들이 잘 정리되어 있습니다.
src/lib/external.ts (2)
Line range hint 10-34
: HTTP 메서드 구현이 잘 되어있습니다.
타입 안전성이 보장되며, 응답 데이터 처리가 일관되게 구현되어 있습니다.
7-7
: 타임아웃 시간 증가에 대한 검토가 필요합니다.
타임아웃을 10초로 증가시킨 것은 API 응답 시간에 문제가 있을 수 있음을 시사합니다. 다음 사항들을 고려해주세요:
- API 응답이 느린 근본적인 원인을 파악하고 개선이 필요한지 검토
- 사용자 경험 향상을 위해 로딩 상태 표시 추가
- 긴 요청 시 사용자가 취소할 수 있는 기능 구현 검토
다음 스크립트로 API 응답 시간이 긴 엔드포인트를 확인해보겠습니다:
✅ Verification successful
타임아웃 설정이 적절하게 구현되어 있습니다.
코드베이스 분석 결과, 다음과 같은 사항들이 확인되었습니다:
- 유튜브 장소 정보 추출 기능에 적절한 로딩 상태(
isLoading
)가 이미 구현되어 있습니다. - 사용자에게 "장소 정보를 추출 중입니다" 메시지와 함께 로딩 상태를 시각적으로 표시합니다.
- 다른 API 인스턴스들(
naver.ts
,axios.ts
)도 5초의 타임아웃을 사용하는 것으로 보아, 유튜브 데이터 추출에는 더 긴 처리 시간이 필요한 것으로 판단됩니다.
타임아웃 증가는 유튜브 데이터 처리의 특성을 고려한 적절한 변경으로 보이며, 이미 사용자 경험을 위한 로딩 상태 처리가 잘 구현되어 있습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# API 엔드포인트 사용 패턴 분석
rg -A 2 "externalInstance\.(get|post|put|patch|delete)" --type ts
Length of output: 1344
Script:
#!/bin/bash
# Search for API endpoint usage patterns in components and pages
rg -A 2 "import.*external'.*" --type ts
Length of output: 236
Script:
#!/bin/bash
# Look for any loading state implementations
rg -A 2 "loading|isLoading" --type ts
Length of output: 2275
Script:
#!/bin/bash
# Search for timeout or error handling patterns
rg -A 2 "(catch|error.*handle|timeout)" --type ts
Length of output: 358
Script:
#!/bin/bash
# Find files that might be related to bookmarks feature
fd -e ts -e tsx bookmark
Length of output: 295
src/components/features/LinkForm/LinkForm.stories.tsx (1)
21-21
: 타입 안정성 개선이 잘 이루어졌습니다!
유니온 타입을 명시적으로 정의하여 타입 안정성을 향상시킨 것이 좋은 개선사항입니다. 이를 통해 컴파일 타임에 잘못된 스텝 이름 사용을 방지할 수 있습니다.
src/pages/Link.tsx (2)
13-13
: 타입 안전성이 향상된 좋은 변경사항입니다!
명시적인 타입 파라미터를 사용하여 코드의 타입 안전성이 개선되었습니다.
19-19
: URL 인코딩 처리 확인이 필요합니다.
encodeURIComponent
가 제거되었는데, 특수 문자가 포함된 URL이 제대로 처리되는지 확인이 필요합니다.
다음 스크립트로 useYoutubePlace
훅에서 URL 인코딩이 처리되는지 확인해보겠습니다:
✅ Verification successful
URL 인코딩이 정상적으로 처리되고 있습니다.
useYoutubePlace
훅 내부에서 encodeURIComponent
를 사용하여 URL을 적절히 인코딩하고 있는 것을 확인했습니다. video/place/${encodeURIComponent(youtubeLink)}
형태로 API를 호출하고 있어 특수 문자가 포함된 URL도 안전하게 처리됩니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: URL 인코딩이 useYoutubePlace 훅 내부에서 처리되는지 확인
# useYoutubePlace 훅 구현 확인
ast-grep --pattern 'useYoutubePlace = ($params) => {
$$$
encodeURIComponent($$$)
$$$
}'
# API 호출부분 확인
rg -A 5 "useYoutubePlace.*youtubeLink"
Length of output: 929
src/components/features/LinkForm/ExtractionStatus.tsx (1)
10-10
: 코드 모듈화가 잘 되었습니다!
비디오 ID 추출 로직을 별도의 유틸리티 함수로 분리한 것은 좋은 접근입니다. 이는 코드의 재사용성을 높이고 유지보수를 용이하게 만듭니다.
package.json (1)
33-33
: jotai 의존성 추가가 적절해 보입니다.
컨텍스트에서 jotai로의 전환은 불필요한 리렌더링을 방지하는데 도움이 될 것으로 보입니다. 다만, 몇 가지 확인이 필요합니다.
번들 크기와 호환성을 확인하기 위해 다음 스크립트를 실행하겠습니다:
✅ Verification successful
Let me verify the React version in the project to ensure compatibility.
jotai 의존성 추가가 안전하게 확인되었습니다
번들 크기가 gzip 기준 3.5KB로 매우 작고, React 18.3.1 버전과 완벽하게 호환됩니다. 불필요한 리렌더링을 방지하기 위한 좋은 선택입니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: jotai 의존성의 번들 크기와 호환성 검사
# 번들 크기 확인
curl -s https://bundlephobia.com/api/size?[email protected] | jq '{gzip: .gzip, size: .size}'
# React 버전 호환성 확인
curl -s https://registry.npmjs.org/jotai/2.10.3 | jq '.peerDependencies'
Length of output: 264
Script:
#!/bin/bash
# React 버전 확인
cat package.json | jq '.dependencies.react, .devDependencies.react'
Length of output: 84
src/components/features/ExtractedPlacesList/ExtractedPlacesList.tsx (1)
16-16
: 컴포넌트 이름이 더 명확해졌습니다!
ExtractedList
에서 ExtractedPlacesList
로의 이름 변경은 컴포넌트의 목적을 더 잘 표현합니다.
src/pages/MapView.tsx (2)
11-11
: 인증 관련 훅 통합이 잘 되었습니다!
새로운 인증 관련 훅들의 통합이 깔끔하게 이루어졌습니다. useAuth
와 useSessionDataLoader
의 도입으로 인증 상태 관리가 체계적으로 구현되었습니다.
Also applies to: 16-16, 20-20
44-48
: 상태 업데이트 시 경쟁 상태 확인이 필요합니다.
extractedData
를 처리하는 useEffect
내에서 setState
와 handleMarkers
를 순차적으로 호출하고 있습니다. 이는 잠재적인 경쟁 상태를 유발할 수 있습니다.
다음 스크립트로 다른 비동기 작업과의 상호작용을 확인해보세요:
src/components/features/BottomSheetContent/BottomSheetContent.tsx (2)
1-2
: 필요한 모듈이 올바르게 임포트되었습니다.
FlowType
과 useBottomFunnel
이 정확하게 임포트되었습니다.
6-7
: 필요한 타입이 정확하게 임포트되었습니다.
YoutubeResponse
와 Place
타입이 올바르게 임포트되었습니다.
src/hooks/common/useBottomFunnel.tsx (1)
22-22
: useAuth
훅의 반환값 구조 확인 필요
const { token } = useAuth();
에서 useAuth
훅이 반환하는 객체에 token
이 포함되어 있는지 확인해주세요. 만약 user
객체 내에 token
이 있다면, 구조 분해 할당을 수정하여 정확한 값을 가져와야 합니다.
export const useNewMarker = (bookmarkId: number, token: string) => { | ||
return useMutation({ | ||
mutationFn: (data: NewMarker) => | ||
post(`api/markers/${bookmarkId}`, data, { | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}), | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
에러 처리 및 토큰 검증 개선이 필요합니다.
코드베이스 분석 결과, 다음과 같은 문제점들이 확인되었습니다:
- HTTP 클라이언트(
axios.ts
)에 전역 에러 처리 로직이 없습니다. - 다른 뮤테이션 훅들도 동일하게 에러 처리가 누락되어 있습니다.
- API 엔드포인트 구조(
api/markers/${bookmarkId}
)는 일관성 있게 사용되고 있습니다.
다음과 같이 개선하는 것을 제안드립니다:
export const useNewMarker = (bookmarkId: number, token: string) => {
if (!token) {
throw new Error('인증 토큰이 필요합니다.');
}
return useMutation({
mutationFn: async (data: NewMarker) => {
try {
return await post(`api/markers/${bookmarkId}`, data, {
headers: {
Authorization: `Bearer ${token}`,
},
});
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 401) {
throw new Error('인증이 만료되었습니다.');
}
throw new Error(`마커 생성 실패: ${error.response?.data?.message || '알 수 없는 오류'}`);
}
throw error;
}
},
});
};
추가로, src/lib/axios.ts
에 전역 에러 처리 인터셉터 추가를 권장합니다.
🔗 Analysis chain
에러 처리 및 보안 개선이 필요합니다.
- 에러 처리가 누락되어 있습니다.
- API 엔드포인트 구조 검증이 필요합니다.
- 토큰 유효성 검사가 필요합니다.
다음과 같이 개선하는 것을 제안드립니다:
export const useNewMarker = (bookmarkId: number, token: string) => {
+ if (!token) {
+ throw new Error('인증 토큰이 필요합니다.');
+ }
+
return useMutation({
mutationFn: (data: NewMarker) =>
post(`api/markers/${bookmarkId}`, data, {
headers: {
Authorization: `Bearer ${token}`,
},
}),
+ onError: (error) => {
+ console.error('마커 생성 중 오류 발생:', error);
+ throw new Error('마커를 생성하는 중 문제가 발생했습니다.');
+ }
});
};
API 엔드포인트 구조를 확인하기 위해 다음 스크립트를 실행해 주세요:
Let me gather more information about error handling patterns and token validation in the codebase.
Let me check how errors are handled in the HTTP client layer since there's no explicit error handling in the hooks.
Let me check the axios instance configuration and error handling setup.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: API 엔드포인트 구조 확인
# API 엔드포인트 패턴 검색
rg -g '*.ts' -g '*.js' "api/markers/[^'\"]+"
# 다른 마커 관련 API 엔드포인트 검색
rg -g '*.ts' -g '*.js' "markers.*post|post.*markers"
Length of output: 377
Script:
#!/bin/bash
# Check error handling patterns in hooks
rg -g '*.ts' 'onError.*throw|throw.*Error' -A 2 -B 2
# Check token validation patterns
rg -g '*.ts' 'if.*!token|token.*validation|validateToken'
# Check other mutation hooks for consistent patterns
ast-grep --pattern 'useMutation({
$$$
})'
Length of output: 1283
Script:
#!/bin/bash
# Check HTTP client implementation
rg -g '*.ts' 'post.*=.*|export.*post' -A 5 -B 5
# Check if there's global error handling
rg -g '*.ts' 'axios.*interceptor|error.*handler|catch.*error' -A 3 -B 3
Length of output: 1955
Script:
#!/bin/bash
# Check axios instance configuration
rg -g '*.ts' 'const.*instance.*=.*axios' -A 10 -B 2
# Check if there's any global error handling setup
rg -g '*.ts' 'instance\.interceptors|errorHandler|handleError'
Length of output: 639
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (1)
src/components/common/Chip/Chip.stories.tsx (1)
30-34
: 스토리의 너비 제한 설정에 대한 개선 제안고정된 너비(w-20)로 제한하는 것은 컴포넌트의 실제 사용 사례를 충분히 보여주지 못할 수 있습니다. 다양한 너비 시나리오를 보여주는 것이 좋겠습니다.
다음과 같이 여러 너비 변형을 추가하는 것을 고려해보세요:
export const Basic: Story = { args: { variant: 'medium', children: '카페', + width: 'default' }, argTypes: { variant: { control: 'inline-radio', options: ['small', 'medium', 'large'], }, + width: { + control: 'inline-radio', + options: ['narrow', 'default', 'wide'], + }, children: { control: 'text', }, }, render: (args) => { return ( - <div className="w-20"> + <div className={ + args.width === 'narrow' ? 'w-20' : + args.width === 'wide' ? 'w-40' : + 'w-28' + }> <Chip variant={args.variant}>{args.children}</Chip> </div> ); }, };
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
📒 Files selected for processing (2)
src/components/features/SearchResultsList/BookmarkSelectionList.tsx
(1 hunks)src/utils/getStorageData.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/utils/getStorageData.ts
const { token } = useAuth(); | ||
const { data } = useBookMarkList(token); | ||
const { clearMarkers } = useMarkers(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
토큰 유효성 검증이 필요합니다
API 호출 전에 토큰의 존재 여부를 확인하는 것이 좋습니다.
const { token } = useAuth();
+if (!token) {
+ return <div>로그인이 필요합니다</div>;
+}
const { data } = useBookMarkList(token);
📝 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.
const { token } = useAuth(); | |
const { data } = useBookMarkList(token); | |
const { clearMarkers } = useMarkers(); | |
const { token } = useAuth(); | |
if (!token) { | |
return <div>로그인이 필요합니다</div>; | |
} | |
const { data } = useBookMarkList(token); | |
const { clearMarkers } = useMarkers(); |
if (bookmarkId !== 0 && selectedPlace) { | ||
mutation.mutate(selectedPlace, { | ||
onSuccess: () => { | ||
sessionStorage.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sessionStorage 클리어 범위를 최소화하세요
sessionStorage.clear()
는 모든 세션 스토리지를 비우므로 의도치 않은 부작용이 발생할 수 있습니다. 필요한 항목만 제거하도록 수정하세요.
-sessionStorage.clear();
+sessionStorage.removeItem('bottomSheetType');
+sessionStorage.removeItem('mapData');
Committable suggestion skipped: line range outside the PR's diff.
<Button variant="primary" size="large" onClick={handleSave}> | ||
저장하기 | ||
</Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
저장 버튼의 비활성화 상태 처리가 필요합니다
북마크가 선택되지 않은 경우 저장 버튼을 비활성화해야 합니다.
-<Button variant="primary" size="large" onClick={handleSave}>
+<Button
+ variant="primary"
+ size="large"
+ onClick={handleSave}
+ disabled={bookmarkId === 0}
+>
저장하기
</Button>
Committable suggestion skipped: line range outside the PR's diff.
<ListCard> | ||
{data?.data.map((item, index) => ( | ||
<> | ||
<div key={item.bookmarkId} className={`flex flex-row justify-between items-center `}> | ||
<div className="flex flex-row gap-4 py-2.5 items-center justify-center"> | ||
{item.youtuberProfile ? ( | ||
<img | ||
src={item.youtuberProfile} | ||
className="w-12 h-12 rounded-full" | ||
alt={`${item.name}의 프로필 이미지`} | ||
/> | ||
) : ( | ||
<Icon name="findy1" className="w-11 h-11" /> | ||
)} | ||
<div className="flex flex-col py-1"> | ||
<Body2 weight="medium">{item.name}</Body2> | ||
<div className="flex flex-row items-center gap-1"> | ||
<Icon name="location" size={15} /> | ||
<Body3 className=" text-gray-500">{item.markersCount}</Body3> | ||
</div> | ||
</div> | ||
</div> | ||
{item.bookmarkType !== 'YOUTUBE' && ( | ||
<Icon | ||
name="check" | ||
className="cursor-pointer h-7" | ||
color={bookmarkId === item.bookmarkId ? 'primary' : 'gray'} | ||
onClick={() => handleToggleSelect(item.bookmarkId)} | ||
/> | ||
)} | ||
</div> | ||
{index < data.data.length - 1 && <hr className="border-dashed pt-2" />} | ||
</> | ||
))} | ||
</ListCard> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
빈 상태와 로딩 상태 처리가 필요합니다
데이터가 없거나 로딩 중일 때의 UI 처리가 누락되었습니다.
<ListCard>
+ {!data?.data.length && (
+ <div className="flex justify-center items-center p-4">
+ <Body2>저장된 북마크가 없습니다</Body2>
+ </div>
+ )}
+ {!data && (
+ <div className="flex justify-center items-center p-4">
+ <LoadingSpinner />
+ </div>
+ )}
{data?.data.map((item, index) => (
Committable suggestion skipped: line range outside the PR's diff.
관련 이슈
close #53
📑 작업 내용
context
를 사용하면 모든 컴포넌트가 리렌더링되는 이슈가 있어jotai
로 변경하였습니다.bookMarkList
,bookMarkDetail
컴포넌트 구현했습니다.SearchResultsList
,ExtractedPlacesList
를 가능한 하나로 합칠 수 있으면 합칠 예정입니다.💬 리뷰 중점 사항/기타 참고 사항
Summary by CodeRabbit
릴리스 노트
jotai
라이브러리 추가로 상태 관리 기능 향상.BookmarkDetail
,BookmarkSelectionList
,SearchResultsList
컴포넌트 추가로 북마크 및 검색 결과 관리 개선.Body
컴포넌트 사용.useBottomFunnel
훅을 통해 사용자 인터랙션을 위한 하단 플로우 관리 기능 추가.extractVideoId
유틸리티 함수 추가로 비디오 ID 추출 기능 개선.getStoredMapData
및clearMapStorage
함수 추가로 세션 저장소 관리 기능 향상.LinkInput
및Landing
컴포넌트의 마진 조정으로 UI 일관성 향상.