-
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
Changes from all commits
43cf42b
c130ab9
94df10c
427fdc0
52fa9ee
8e1402a
a4f1363
45f9d82
0253576
634c011
471ce67
21a6258
5097ca7
71c20ef
276116a
af76cf5
4a58a5b
036d042
0b3d483
30f9c62
d233c08
69c0b07
5580580
1e48dda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,13 @@ | ||
import { Outlet } from 'react-router-dom'; | ||
|
||
import { Layout } from './components/common/Layout'; | ||
import { MapDataProvider } from './contexts/MapContext'; | ||
import { MarkerProvider } from './contexts/MarkerContext'; | ||
import { useAuth } from './hooks/auth/useAuth'; | ||
|
||
export const App = () => { | ||
useAuth(); | ||
return ( | ||
<MapDataProvider> | ||
<MarkerProvider> | ||
<Layout> | ||
<Outlet /> | ||
</Layout> | ||
</MarkerProvider> | ||
</MapDataProvider> | ||
<Layout> | ||
<Outlet /> | ||
</Layout> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,64 @@ | ||||||||||||||||||||||||||||
import { useEffect } from 'react'; | ||||||||||||||||||||||||||||
import { useAtom } from 'jotai'; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
import { Chip } from '@/components/common/Chip'; | ||||||||||||||||||||||||||||
import { Icon } from '@/components/common/Icon'; | ||||||||||||||||||||||||||||
import { ListCard } from '@/components/common/ListCard'; | ||||||||||||||||||||||||||||
import { Body1, Body2, Body4 } from '@/components/common/Typography'; | ||||||||||||||||||||||||||||
import { markersAtom } from '@/contexts/MarkerAtom'; | ||||||||||||||||||||||||||||
import { useMarkerList } from '@/hooks/api/marker/useMarkerList'; | ||||||||||||||||||||||||||||
import { useAuth } from '@/hooks/auth/useAuth'; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
type Props = { bookmarkId: number; onPrev: () => void }; | ||||||||||||||||||||||||||||
export const BookmarkDetail = ({ bookmarkId, onPrev }: Props) => { | ||||||||||||||||||||||||||||
const { token } = useAuth(); | ||||||||||||||||||||||||||||
const { data } = useMarkerList(bookmarkId, token); | ||||||||||||||||||||||||||||
const [, setMarkers] = useAtom(markersAtom); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||
if (data?.markers?.data) { | ||||||||||||||||||||||||||||
setMarkers(data.markers.data); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
}, [data?.markers?.data, setMarkers]); | ||||||||||||||||||||||||||||
Comment on lines
+18
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion useEffect 정리(cleanup) 함수 추가 및 조건부 로직 개선이 필요합니다. 컴포넌트가 언마운트될 때 마커를 정리하지 않고 있으며, 데이터 접근 방식이 복잡합니다. 다음과 같이 개선해보세요: useEffect(() => {
- if (data?.markers?.data) {
- setMarkers(data.markers.data);
+ const markers = data?.markers?.data ?? [];
+ setMarkers(markers);
+
+ return () => {
+ setMarkers([]);
}
- }, [data?.markers?.data, setMarkers]);
+}, [data?.markers?.data, setMarkers]); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||||
<div className="flex flex-row items-center"> | ||||||||||||||||||||||||||||
<Icon | ||||||||||||||||||||||||||||
name="back" | ||||||||||||||||||||||||||||
size={30} | ||||||||||||||||||||||||||||
onClick={() => { | ||||||||||||||||||||||||||||
setMarkers([]); | ||||||||||||||||||||||||||||
onPrev(); | ||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||
className="cursor-pointer" | ||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||
<Body1 className="my-4 mx-3" weight="semibold" onClick={onPrev}> | ||||||||||||||||||||||||||||
{data?.bookmarkName}의 핀디 리스트 | ||||||||||||||||||||||||||||
</Body1> | ||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||
<ListCard> | ||||||||||||||||||||||||||||
{data?.markers.data.map((item, index) => ( | ||||||||||||||||||||||||||||
<div key={item.markerId}> | ||||||||||||||||||||||||||||
<div | ||||||||||||||||||||||||||||
className={`flex flex-row justify-between items-center ${index !== data.markers.data.length - 1 && 'pb-2'}`} | ||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||
<div className="flex flex-col gap-1 py-2"> | ||||||||||||||||||||||||||||
<div className="flex flex-row gap-3 items-center"> | ||||||||||||||||||||||||||||
<Body2 className="text-primary">{item.title}</Body2> | ||||||||||||||||||||||||||||
{typeof item.category === 'object' && ( | ||||||||||||||||||||||||||||
<Chip variant="medium">{item.category.majorCategory}</Chip> | ||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||
<Body4 className="pt-1 " weight="normal"> | ||||||||||||||||||||||||||||
{item.address} | ||||||||||||||||||||||||||||
</Body4> | ||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||
{index < data.markers.data.length - 1 && <hr className="border-dashed pt-2" />} | ||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||
))} | ||||||||||||||||||||||||||||
</ListCard> | ||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||
Comment on lines
+24
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 접근성 개선 및 코드 구조화가 필요합니다.
다음과 같은 개선사항을 제안드립니다: +const CLASSNAMES = {
+ container: 'flex flex-row items-center',
+ backIcon: 'cursor-pointer',
+ title: 'my-4 mx-3',
+ listItem: (isLast: boolean) => `flex flex-row justify-between items-center ${!isLast && 'pb-2'}`,
+ divider: 'border-dashed pt-2',
+};
return (
<div>
- <div className="flex flex-row items-center">
+ <div className={CLASSNAMES.container}>
<Icon
name="back"
size={30}
onClick={() => {
setMarkers([]);
onPrev();
}}
- className="cursor-pointer"
+ className={CLASSNAMES.backIcon}
+ role="button"
+ tabIndex={0}
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ setMarkers([]);
+ onPrev();
+ }
+ }}
/>
- <Body1 className="my-4 mx-3" weight="semibold" onClick={onPrev}>
+ <Body1
+ className={CLASSNAMES.title}
+ weight="semibold"
+ onClick={onPrev}
+ role="button"
+ tabIndex={0}
+ onKeyPress={(e) => e.key === 'Enter' && onPrev()}
+ >
{data?.bookmarkName}의 핀디 리스트
</Body1>
</div>
|
||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { BookmarkDetail } from './BookmarkDetail'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,53 @@ | ||
import { Icon } from '@/components/common/Icon'; | ||
import { ListCard } from '@/components/common/ListCard'; | ||
import { Body1, Body2, Body3 } from '@/components/common/Typography'; | ||
import { useBookMarkList } from '@/hooks/api/bookmarks/useBookMarkList'; | ||
import { useAuth } from '@/hooks/auth/useAuth'; | ||
|
||
export const BookmarkList = () => { | ||
type Props = { onNext: (bookmarkId: number) => void }; | ||
export const BookmarkList = ({ onNext }: Props) => { | ||
const { token } = useAuth(); | ||
const { data } = useBookMarkList(token); | ||
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 에러 처리가 누락되었습니다
다음과 같이 수정을 제안합니다: - const { data } = useBookMarkList(token);
+ const { data, error, isLoading } = useBookMarkList(token);
+
+ if (isLoading) return <LoadingSpinner />;
+ if (error) return <ErrorMessage message="북마크 목록을 불러오는데 실패했습니다" />;
|
||
|
||
const handleBookmarkClick = (bookmarkId: number) => { | ||
onNext(bookmarkId); | ||
}; | ||
return ( | ||
<div> | ||
나의 핀디 리스트 | ||
<ListCard>리스트 조회</ListCard> | ||
<Body1 className="my-4 mx-3" weight="semibold"> | ||
나의 핀디 리스트 | ||
</Body1> | ||
<ListCard> | ||
{data?.data.map((item, index) => ( | ||
keemsebin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<> | ||
<div | ||
Comment on lines
+21
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fragment가 아닌 최상위 div 요소에 다음과 같이 수정해주세요: - {data?.data.map((item, index) => (
- <>
- <div
- key={item.bookmarkId}
+ {data?.data.map((item, index) => (
+ <div
+ key={item.bookmarkId} Also applies to: 47-49 |
||
key={item.bookmarkId} | ||
onClick={() => handleBookmarkClick(item.bookmarkId)} | ||
className="flex flex-row justify-between items-center cursor-pointer" | ||
> | ||
<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> | ||
</div> | ||
{index < data.data.length - 1 && <hr className="border-dashed pt-2" />} | ||
</> | ||
))} | ||
</ListCard> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { BookmarkList } from './BookmarkList'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,11 @@ | ||
import { YoutubeResponse } from '@/hooks/api/link/useYoutubePlace'; | ||
import { useAuth } from '@/hooks/auth/useAuth'; | ||
import { Place } from '@/types/naver'; | ||
import { FlowType } from '@/constants/funnelStep'; | ||
import { useBottomFunnel } from '@/hooks/common/useBottomFunnel'; | ||
|
||
import { ExtractedPlaces } from './ExtractedPlaces'; | ||
import { SearchResult } from './SearchResult'; | ||
import { BottomSheetContentProps } from './types'; | ||
|
||
import { BookmarkList } from '../BookmarkList/BookmarkList'; | ||
import { YoutubeResponse } from '../../../hooks/api/link/useYoutubePlace'; | ||
import { Place } from '../../../types/naver'; | ||
|
||
export const BottomSheetContent = ({ type, data }: BottomSheetContentProps) => { | ||
const { token } = useAuth(); | ||
|
||
if (type === 'search') { | ||
return <SearchResult places={data as Place[]} />; | ||
} | ||
|
||
if (type === 'extract') { | ||
return <ExtractedPlaces places={data as YoutubeResponse} />; | ||
} | ||
|
||
if (token && type === 'list') { | ||
return <BookmarkList />; | ||
} | ||
|
||
// TODO : 로그인하지 않은 경우 로그인 유도 | ||
return ( | ||
<div className=" flex items-center justify-center py-8"> | ||
<p className="text-gray-500">로그인한 사용자만 즐겨찾기가 가능합니다.</p> | ||
</div> | ||
); | ||
return useBottomFunnel({ type: type as FlowType, data: data as Place[] | YoutubeResponse }); | ||
}; | ||
keemsebin marked this conversation as resolved.
Show resolved
Hide resolved
|
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,11 +13,12 @@ import { useMarkers } from '@/hooks/common/useMarkers'; | |
import { Login } from '../LoginModal'; | ||
|
||
type Props = { places: YoutubeResponse; onNext: () => void }; | ||
export const ExtractedList = ({ places, onNext }: Props) => { | ||
const { clearMarkers } = useMarkers(); | ||
export const ExtractedPlacesList = ({ places, onNext }: Props) => { | ||
const [selectedIds, setSelectedIds] = useState<number[]>([]); | ||
const [isOpen, setIsOpen] = useState<boolean>(false); | ||
|
||
const { token } = useAuth(); | ||
const { clearMarkers } = useMarkers(); | ||
const { mutate: bookmarkMutate } = useYoutubeBookmark(token); | ||
|
||
const handleToggleSelect = (id: number) => { | ||
|
@@ -32,26 +33,20 @@ export const ExtractedList = ({ places, onNext }: Props) => { | |
return; | ||
} | ||
|
||
const filteredPlaces = places.places.filter((place) => | ||
selectedIds.includes(place.id as number) | ||
); | ||
|
||
const savePlaces = { | ||
...places, | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
places: filteredPlaces.map(({ id, ...rest }) => rest), | ||
places: places.places | ||
.filter((place) => selectedIds.includes(place.id as number)) | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
.map(({ id, ...placeData }) => placeData), | ||
}; | ||
|
||
bookmarkMutate(savePlaces, { | ||
onSuccess: () => { | ||
sessionStorage.removeItem('bottomSheetType'); | ||
sessionStorage.removeItem('mapData'); | ||
sessionStorage.clear(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Based on the search results, I can now generate the final response as we have a clear picture of sessionStorage usage in the codebase. The search shows that sessionStorage is consistently used for two specific items: 'mapData' and 'bottomSheetType', and there's already a utility function 세션 스토리지 클리어 시 현재 코드베이스에서는 'mapData'와 'bottomSheetType' 두 가지 항목만 세션 스토리지에서 관리되고 있습니다.
🔗 Analysis chainsessionStorage 클리어 범위를 검토해주세요.
다음 스크립트로 sessionStorage 사용을 확인해보세요: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# sessionStorage 사용 패턴 분석
rg "sessionStorage\." -A 2 -B 2
Length of output: 2386 |
||
clearMarkers(); | ||
onNext(); | ||
}, | ||
onError: (error) => { | ||
console.error('저장 실패', error); | ||
}, | ||
}); | ||
}; | ||
return ( | ||
|
@@ -86,13 +81,13 @@ export const ExtractedList = ({ places, onNext }: Props) => { | |
<Chip variant="medium">{item.category.majorCategory}</Chip> | ||
)} | ||
</div> | ||
<Body4 className="pt-1 " weight="normal"> | ||
<Body4 className="pt-1" weight="normal"> | ||
{item.address} | ||
</Body4> | ||
</div> | ||
<Icon | ||
name="check" | ||
className="cursor-pointer h-7" | ||
className="cursor-pointer h-7 w-7 flex-shrink-0" | ||
color={selectedIds.includes(item.id as number) ? 'primary' : 'gray'} | ||
onClick={() => handleToggleSelect(item.id as number)} | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { ExtractedPlacesList } from './ExtractedPlacesList'; |
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.
토큰과 데이터에 대한 오류 처리가 필요합니다.
현재 구현에서는 토큰이 없거나 데이터 로딩 실패 시의 처리가 누락되어 있습니다.
다음과 같은 개선이 필요합니다:
📝 Committable suggestion