From 578cb255df7c92a04bc8059ad7e7a4018212a93b Mon Sep 17 00:00:00 2001 From: yshls Date: Tue, 27 Oct 2026 12:31:21 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(map):=20=EA=B3=B5=EC=9C=A0=20=ED=95=80?= =?UTF-8?q?=20=EC=B0=8D=EA=B8=B0=20=EC=A0=84=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - handleAddPin 진입 시 isLoggedIn 검사 - 비로그인이면 toast('로그인이 필요해요') 후 얼리 리턴 - Login01Icon (Hugeicons) 사용 Co-Authored-By: Claude Sonnet 4.6 --- src/features/map/collaboration/CollabPinsOverlay.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/features/map/collaboration/CollabPinsOverlay.tsx b/src/features/map/collaboration/CollabPinsOverlay.tsx index 38c40843..11090a21 100644 --- a/src/features/map/collaboration/CollabPinsOverlay.tsx +++ b/src/features/map/collaboration/CollabPinsOverlay.tsx @@ -3,9 +3,10 @@ import { useEffect, useRef } from 'react'; import styled from '@emotion/styled'; import { HugeiconsIcon } from '@hugeicons/react'; -import { Location01Icon, Cancel01Icon, UserMultiple02Icon } from '@hugeicons/core-free-icons'; +import { Location01Icon, Cancel01Icon, UserMultiple02Icon, Login01Icon } from '@hugeicons/core-free-icons'; import { toast } from 'sonner'; import { useSelf } from '@liveblocks/react'; +import { useAuth } from '@/features/auth'; import { useMapStore } from '../hooks/useMapStore'; import { useCollabPins } from './useCollabPins'; import { ringShadow, palette } from '@/design-system/tokens'; @@ -152,6 +153,7 @@ export function CollabPinsOverlay() { const viewportItems = useMapStore((s) => s.viewportItems); const detailId = useMapStore((s) => s.detailId); const me = useSelf(); + const { isLoggedIn } = useAuth(); const { pins, addPin, removePin } = useCollabPins(); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -280,6 +282,14 @@ export function CollabPinsOverlay() { function handleAddPin() { if (!selectedPlace) return; + if (!isLoggedIn) { + toast('로그인이 필요해요', { + icon: , + description: '핀 공유는 로그인 후 사용할 수 있어요.', + duration: 3000, + }); + return; + } const pin: CollabPin = { placeId: selectedPlace.id, placeName: selectedPlace.name, From 1ca844fada78eefb451c27d3bd4951f1dc898d65 Mon Sep 17 00:00:00 2001 From: yshls Date: Tue, 27 Oct 2026 12:33:13 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix(map):=20=EA=B3=B5=EC=9C=A0=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20URL=20=EB=B3=B4=EC=A1=B4=20=E2=80=94=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20toast=EC=97=90=EC=84=9C=20=EC=A7=81?= =?UTF-8?q?=EC=A0=91=20loginWithKakao=20=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - toast action '로그인하기' 클릭 시 현재 페이지(/map?collab=uuid)를 returnTo로 유지 - /auth/login 경유 시 returnTo가 /mypage로 덮어써지는 문제 방지 Co-Authored-By: Claude Sonnet 4.6 --- src/features/map/collaboration/CollabPinsOverlay.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/features/map/collaboration/CollabPinsOverlay.tsx b/src/features/map/collaboration/CollabPinsOverlay.tsx index 11090a21..6f4e7b68 100644 --- a/src/features/map/collaboration/CollabPinsOverlay.tsx +++ b/src/features/map/collaboration/CollabPinsOverlay.tsx @@ -153,7 +153,7 @@ export function CollabPinsOverlay() { const viewportItems = useMapStore((s) => s.viewportItems); const detailId = useMapStore((s) => s.detailId); const me = useSelf(); - const { isLoggedIn } = useAuth(); + const { isLoggedIn, loginWithKakao } = useAuth(); const { pins, addPin, removePin } = useCollabPins(); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -286,7 +286,8 @@ export function CollabPinsOverlay() { toast('로그인이 필요해요', { icon: , description: '핀 공유는 로그인 후 사용할 수 있어요.', - duration: 3000, + duration: 5000, + action: { label: '로그인하기', onClick: loginWithKakao }, }); return; } From 3a50588803dadfc4515c53b60c3ff8015d00f414 Mon Sep 17 00:00:00 2001 From: yshls Date: Tue, 27 Oct 2026 12:44:21 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat(map):=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=A8=EB=84=90=20=EB=82=B4=20=EC=9C=84=EC=B9=98=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EA=B1=B0=EB=A6=AC=EC=88=9C=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - listInfoPlaces에 sort/lat/lng 파라미터 추가 - userLocation 있으면 sort=DISTANCE + lat/lng 전달, 없으면 기본 REGION_NAME - useInfoMapData: userLocation 변경 시 목록 재조회 (의존성 추가) - InfoPlaceList 페이지네이션도 동일한 위치 파라미터 유지 Co-Authored-By: Claude Sonnet 4.6 --- src/features/map/components/InfoPlaceList.tsx | 4 ++++ src/features/map/hooks/useInfoMapData.ts | 4 +++- src/features/map/services/infoMap.service.ts | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/features/map/components/InfoPlaceList.tsx b/src/features/map/components/InfoPlaceList.tsx index e1fb2ec0..3c84cf6b 100644 --- a/src/features/map/components/InfoPlaceList.tsx +++ b/src/features/map/components/InfoPlaceList.tsx @@ -241,10 +241,14 @@ export default function InfoPlaceList() { const requestSnapshotId = store.listSnapshotId; store.setIsListLoading(true); + const requestUserLocation = store.userLocation; listInfoPlaces({ category: requestCategory, regionCode: requestRegionCode, cursor, + ...(requestUserLocation + ? { sort: 'DISTANCE', lat: requestUserLocation.lat, lng: requestUserLocation.lng } + : {}), signal: ctrl.signal, }) .then((page) => { diff --git a/src/features/map/hooks/useInfoMapData.ts b/src/features/map/hooks/useInfoMapData.ts index 93035de4..ee41a301 100644 --- a/src/features/map/hooks/useInfoMapData.ts +++ b/src/features/map/hooks/useInfoMapData.ts @@ -65,6 +65,7 @@ export function useInfoMapData(enabled = true) { const mode = useMapStore((s) => s.mode); const infoCategory = useMapStore((s) => s.infoCategory); const infoRegionCode = useMapStore((s) => s.infoRegionCode); + const userLocation = useMapStore((s) => s.userLocation); const committedViewport = useMapStore((s) => s.committedViewport); const infoListReloadNonce = useMapStore((s) => s.infoListReloadNonce); const infoViewportReloadNonce = useMapStore((s) => s.infoViewportReloadNonce); @@ -98,6 +99,7 @@ export function useInfoMapData(enabled = true) { listInfoPlaces({ category: infoCategory, regionCode: infoRegionCode, + ...(userLocation ? { sort: 'DISTANCE', lat: userLocation.lat, lng: userLocation.lng } : {}), signal: ctrl.signal, timeoutMs, }) @@ -141,7 +143,7 @@ export function useInfoMapData(enabled = true) { }); return () => ctrl.abort(); - }, [enabled, mode, infoCategory, infoRegionCode, infoListReloadNonce]); + }, [enabled, mode, infoCategory, infoRegionCode, userLocation?.lat, userLocation?.lng, infoListReloadNonce]); // ── Viewport ─────────────────────────────────────────────────────────────── useEffect(() => { diff --git a/src/features/map/services/infoMap.service.ts b/src/features/map/services/infoMap.service.ts index 7900c9ff..41b1cf20 100644 --- a/src/features/map/services/infoMap.service.ts +++ b/src/features/map/services/infoMap.service.ts @@ -13,6 +13,9 @@ export interface ListInfoPlacesInput { regionCode?: string | null; cursor?: string | null; limit?: number; + sort?: 'REGION_NAME' | 'NAME' | 'DISTANCE'; + lat?: number | null; + lng?: number | null; signal?: AbortSignal; timeoutMs?: number; } @@ -58,6 +61,11 @@ export async function listInfoPlaces(input: ListInfoPlacesInput): Promise('/map/info/places', { method: 'GET',