diff --git a/src/api/place.ts b/src/api/place.ts index 0f9d10f..cf6f7b4 100644 --- a/src/api/place.ts +++ b/src/api/place.ts @@ -36,6 +36,7 @@ type GetPlaceDetailRequest = { const toPinSearchPlace = (item: PlaceSearchItem): PinSearchPlace => ({ id: `${item.resultType}:${item.providerPlaceId ?? `${item.latitude}:${item.longitude}`}`, creatorName: item.hasPin ? (item.firstPinCreatorNickname ?? undefined) : undefined, + hasPin: item.hasPin, category: item.category || '장소', placeName: item.placeName, address: item.roadAddress || item.address, @@ -59,6 +60,7 @@ const toRecentPinSearchPlace = (item: PlaceSearchHistoryItem): PinSearchPlace => placeId: item.placeId, searchHistoryId: item.historyId, creatorName: item.hasPin ? (item.firstPinCreatorNickname ?? undefined) : undefined, + hasPin: item.hasPin, category: item.category || '장소', placeName: item.placeName, address: item.roadAddress || item.address, @@ -207,6 +209,7 @@ export async function selectSearchPlace({ withinAccessRange: selection.withinAccessRange, id: `place:${selection.placeId}`, creatorName: selection.hasPin ? (selection.firstPinCreatorNickname ?? undefined) : undefined, + hasPin: selection.hasPin, placeName: selection.placeName, address: selection.roadAddress || selection.address, distance: selection.distanceMeters, diff --git a/src/components/ui/BottomSheet.tsx b/src/components/ui/BottomSheet.tsx index bf27a2a..c7569c0 100644 --- a/src/components/ui/BottomSheet.tsx +++ b/src/components/ui/BottomSheet.tsx @@ -236,6 +236,21 @@ function BottomSheet({ [trackingElementRef], ); + // vaulOpen은 keepOpenWhileMounted 중엔 open=false에도 true로 남으니, 감시 구간은 open 기준으로 잡는다. + React.useEffect(() => { + if (!open) return; + const body = document.body; + const restorePointerEvents = () => { + if (body.style.pointerEvents === 'none') { + body.style.pointerEvents = ''; + } + }; + restorePointerEvents(); + const observer = new MutationObserver(restorePointerEvents); + observer.observe(body, { attributes: true, attributeFilter: ['style'] }); + return () => observer.disconnect(); + }, [open]); + const isFullPage = snapPoints.length > 1 && activeSnap === lastSnap; const collapse = React.useCallback(() => { diff --git a/src/features/pin/types.ts b/src/features/pin/types.ts index fd286bd..300c3ac 100644 --- a/src/features/pin/types.ts +++ b/src/features/pin/types.ts @@ -305,4 +305,9 @@ export type PinSearchPlace = PlaceResult & { * 상단 검색창 대신 ‘뒤로가기’를 표시한다. */ showMapBackButton?: boolean; + /** + * 이 장소에 등록된 핀이 있는지. creatorName이 없는 경로(예: 인기 장소 카드)에서도 + * 마커를 신규 미등록 장소용 기본 아이콘으로 잘못 그리지 않기 위해 별도로 둔다. + */ + hasPin?: boolean; }; diff --git a/src/hooks/useYouTubeClipPlayer.ts b/src/hooks/useYouTubeClipPlayer.ts index 3e02917..3114d9e 100644 --- a/src/hooks/useYouTubeClipPlayer.ts +++ b/src/hooks/useYouTubeClipPlayer.ts @@ -165,7 +165,9 @@ export function useYouTubeClipPlayer({ enabled = true }: UseYouTubeClipPlayerOpt clearStopTimer(); activeKeyRef.current = null; setPlayingKey(null); - playerRef.current?.pauseVideo(); + if (playerReadyRef.current) { + playerRef.current?.pauseVideo(); + } }, [clearStopTimer]); const scheduleClipStop = useCallback( diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 49bf8a0..8a2a39d 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -248,6 +248,7 @@ export default function HomePage() { address: placeDetail.roadAddress || placeDetail.address, distance: placeDetail.distanceMeters, creatorName: place.creatorName, + hasPin: placeDetail.hasPin, bookmarkedByMe: placeDetail.bookmarkedByMe, isMine: placeDetail.pinnedByMe, selectionLocation: userLocation, diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx index 0f5ef21..b7fe83a 100644 --- a/src/pages/MapPage.tsx +++ b/src/pages/MapPage.tsx @@ -179,10 +179,15 @@ const MapPage: React.FC = ({ // CTA(focusedFeedPin)와 말풍선(mapFocusPin)을 분리해, 찜한 노래에서는 내 등록 곡이 없어도 인기 PIN 말풍선은 유지한다. const overlayFocusPin = selectedMapPlace?.mapFocusPin ?? selectedMapPlace?.focusedFeedPin; const isNewUnregisteredPlace = Boolean( - selectedMapPlace && !overlayFocusPin && !selectedMapPlace.creatorName, + selectedMapPlace && + !overlayFocusPin && + !selectedMapPlace.creatorName && + !selectedMapPlace.hasPin, ); const focusedMapPinId = - selectedMapPlace && (overlayFocusPin || isNewUnregisteredPlace) ? selectedMapPlace.id : null; + selectedMapPlace && (overlayFocusPin || isNewUnregisteredPlace || selectedMapPlace.hasPin) + ? selectedMapPlace.id + : null; const displayMapPins = useMemo(() => { let pins = mapPins;