-
-
- {/* SDK 로드 에러 오버레이 */}
- {mapError && (
-
- )}
+
+
+ {/* 지도 + 리스트 영역
+ isMapMounted: 첫 검색 전 / 에러는 마운트하지 않음
+ isMapVisible: noResults일 때 Map 인스턴스를 유지한 채 CSS로만 숨김 */}
+ {isMapMounted && (
+
+
+
+
+ {searchState === 'searching' ? (
+
+ ) : (
+
+ )}
+
+
+ )}
- {/* 검색 전 안내 메시지 오버레이 */}
- {!isInitialized && !mapError && (
-
-
-
-
장소를 검색하면
-
지도에 표시됩니다
-
-
- )}
+ {/* 검색 결과 없음 */}
+ {searchState === 'noResults' && (
+
+ )}
- {/* 장소 리스트 */}
-
- {searchError && (
-
- )}
-
+ {/* SDK 오류 또는 검색 오류 */}
+ {searchState === 'error' && (
+
-
+ )}
-
-
-
-
)
diff --git a/src/features/meetings/components/index.ts b/src/features/meetings/components/index.ts
index dbb1aa8..68df722 100644
--- a/src/features/meetings/components/index.ts
+++ b/src/features/meetings/components/index.ts
@@ -1,9 +1,9 @@
-export { default as MapModal } from './MapModal'
export { default as MeetingApprovalItem } from './MeetingApprovalItem'
export { default as MeetingApprovalList } from './MeetingApprovalList'
export { default as MeetingApprovalListSkeleton } from './MeetingApprovalListSkeleton'
export { default as MeetingDetailButton } from './MeetingDetailButton'
-export { MeetingDetailHeader } from './MeetingDetailHeader'
-export { MeetingDetailInfo } from './MeetingDetailInfo'
+export { default as MeetingDetailHeader } from './MeetingDetailHeader'
+export { default as MeetingDetailInfo } from './MeetingDetailInfo'
export { default as PlaceList } from './PlaceList'
+export { default as PlaceListSkeleton } from './PlaceListSkeleton'
export { default as PlaceSearchModal } from './PlaceSearchModal'
diff --git a/src/features/meetings/hooks/index.ts b/src/features/meetings/hooks/index.ts
index 34d21ae..615edca 100644
--- a/src/features/meetings/hooks/index.ts
+++ b/src/features/meetings/hooks/index.ts
@@ -4,10 +4,9 @@ export * from './useConfirmMeeting'
export * from './useCreateMeeting'
export * from './useDeleteMeeting'
export * from './useJoinMeeting'
-export * from './useKakaoMap'
-export * from './useKakaoPlaceSearch'
export * from './useMeetingApprovals'
export * from './useMeetingDetail'
export * from './useMeetingForm'
+export * from './usePlaceSearch'
export * from './useRejectMeeting'
export * from './useUpdateMeeting'
diff --git a/src/features/meetings/hooks/useKakaoMap.ts b/src/features/meetings/hooks/useKakaoMap.ts
deleted file mode 100644
index 08f01fd..0000000
--- a/src/features/meetings/hooks/useKakaoMap.ts
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * @file useKakaoMap.ts
- * @description Kakao Maps 지도 및 마커 관리 훅
- */
-
-import { useRef, useState } from 'react'
-
-import type { KakaoPlace } from '../kakaoMap.types'
-import { loadKakaoSdk } from '../loadKakaoSdk'
-
-export type UseKakaoMapOptions = {
- /** 초기 중심 좌표 */
- initialCenter?: { lat: number; lng: number }
- /** 초기 줌 레벨 */
- initialLevel?: number
-}
-
-export function useKakaoMap({ initialCenter, initialLevel = 3 }: UseKakaoMapOptions = {}) {
- const [mapElement, setMapElement] = useState
(null)
- const [isInitialized, setIsInitialized] = useState(false)
- const [error, setError] = useState(null)
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const mapRef = useRef(null)
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const markersRef = useRef([])
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const infowindowRef = useRef(null)
-
- const defaultCenter = useRef(initialCenter ?? { lat: 37.566826, lng: 126.9786567 })
-
- // 마커 제거
- const clearMarkers = () => {
- markersRef.current.forEach((marker) => {
- marker.setMap(null)
- })
- markersRef.current = []
- }
-
- // 인포윈도우 닫기
- const closeInfoWindow = () => {
- infowindowRef.current?.close()
- }
-
- // HTML escape 유틸리티
- const escapeHtml = (text: string) => {
- const div = document.createElement('div')
- div.textContent = text
- return div.innerHTML
- }
-
- // 인포윈도우 열기
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const openInfoWindow = (marker: any, title: string) => {
- if (!mapRef.current || !infowindowRef.current) return
- const escapedTitle = escapeHtml(title)
- infowindowRef.current.setContent(`${escapedTitle}
`)
- infowindowRef.current.open(mapRef.current, marker)
- }
-
- // 지도 수동 초기화
- const initializeMap = async () => {
- if (!mapElement) {
- console.warn('Map element not ready')
- return false
- }
-
- if (mapRef.current) {
- // 이미 초기화된 경우 relayout만 실행
- mapRef.current.relayout()
- return true
- }
-
- try {
- await loadKakaoSdk()
- } catch (err) {
- const message = err instanceof Error ? err.message : '카카오 지도 SDK 로드에 실패했습니다.'
- setError(message)
- return false
- }
-
- const kakao = window.kakao
-
- if (!kakao?.maps) {
- const message = '카카오 지도 SDK가 로드되지 않았습니다.'
- console.error('[카카오 지도]', message)
- setError(message)
- return false
- }
-
- // 지도 생성
- const map = new kakao.maps.Map(mapElement, {
- center: new kakao.maps.LatLng(defaultCenter.current.lat, defaultCenter.current.lng),
- level: initialLevel,
- })
-
- mapRef.current = map
-
- infowindowRef.current = new kakao.maps.InfoWindow({ zIndex: 1 })
- setError(null)
- setIsInitialized(true)
-
- // Portal/Modal에서 사이즈 계산 이슈 방지
- setTimeout(() => {
- map.relayout()
- map.setCenter(new kakao.maps.LatLng(defaultCenter.current.lat, defaultCenter.current.lng))
- }, 0)
-
- return true
- }
-
- // 지도 정리
- const cleanup = () => {
- clearMarkers()
- closeInfoWindow()
- mapRef.current = null
- infowindowRef.current = null
- setIsInitialized(false)
- setError(null)
- }
-
- // 장소 목록에 대한 마커 렌더링
- const renderMarkers = (places: KakaoPlace[]) => {
- if (!mapRef.current || !window.kakao) return
-
- const kakao = window.kakao
- const map = mapRef.current
-
- clearMarkers()
- closeInfoWindow()
-
- const bounds = new kakao.maps.LatLngBounds()
-
- places.forEach((place) => {
- const position = new kakao.maps.LatLng(Number(place.y), Number(place.x))
-
- const marker = new kakao.maps.Marker({
- position,
- map,
- })
-
- // 마커 hover 이벤트
- kakao.maps.event.addListener(marker, 'mouseover', () => {
- openInfoWindow(marker, place.place_name)
- })
- kakao.maps.event.addListener(marker, 'mouseout', () => {
- closeInfoWindow()
- })
-
- markersRef.current.push(marker)
- bounds.extend(position)
- })
-
- // 마커들이 모두 보이도록 bounds 조정
- if (places.length > 0) {
- map.setBounds(bounds)
- }
- }
-
- // 특정 좌표로 지도 중심 이동
- const setCenter = (lat: number, lng: number) => {
- if (!mapRef.current || !window.kakao) return
- const kakao = window.kakao
- const position = new kakao.maps.LatLng(lat, lng)
- mapRef.current.setCenter(position)
- }
-
- return {
- mapElement: setMapElement,
- isInitialized,
- error,
- initializeMap,
- renderMarkers,
- closeInfoWindow,
- openInfoWindow,
- setCenter,
- cleanup,
- }
-}
diff --git a/src/features/meetings/hooks/useKakaoPlaceSearch.ts b/src/features/meetings/hooks/useKakaoPlaceSearch.ts
deleted file mode 100644
index 0bf9e44..0000000
--- a/src/features/meetings/hooks/useKakaoPlaceSearch.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * @file useKakaoPlaceSearch.ts
- * @description Kakao Places API 검색 로직 훅
- */
-
-import { useRef, useState } from 'react'
-
-import type { KakaoPlace } from '../kakaoMap.types'
-
-export type UseKakaoPlaceSearchOptions = {
- /** 검색 성공 콜백 */
- onSearchSuccess?: (places: KakaoPlace[]) => void
- /** 검색 오류 콜백 */
- onSearchError?: (message: string) => void
-}
-
-export function useKakaoPlaceSearch({
- onSearchSuccess,
- onSearchError,
-}: UseKakaoPlaceSearchOptions = {}) {
- const [places, setPlaces] = useState([])
- const [error, setError] = useState(null)
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const placesServiceRef = useRef(null)
-
- // 검색 실행
- const search = (searchKeyword: string) => {
- if (!searchKeyword.trim()) {
- return false
- }
-
- const kakao = window.kakao
- if (!kakao?.maps?.services) {
- return false
- }
-
- // Places 서비스
- if (!placesServiceRef.current) {
- placesServiceRef.current = new kakao.maps.services.Places()
- }
-
- const ps = placesServiceRef.current
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ps.keywordSearch(searchKeyword, (data: KakaoPlace[], status: any) => {
- if (status === kakao.maps.services.Status.OK) {
- setError(null)
- setPlaces(data)
- onSearchSuccess?.(data)
- } else if (status === kakao.maps.services.Status.ZERO_RESULT) {
- setError(null)
- setPlaces([])
- onSearchSuccess?.([])
- } else {
- // Status.ERROR: 네트워크 오류, 서버 오류 등 다양한 원인으로 발생
- const message = '검색 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'
- setError(message)
- setPlaces([])
- onSearchError?.(message)
- console.error('[카카오 장소 검색] 오류 발생 - status:', status)
- }
- })
-
- return true
- }
-
- // 검색 상태 초기화
- const reset = () => {
- setPlaces([])
- setError(null)
- placesServiceRef.current = null
- }
-
- return {
- places,
- error,
- search,
- reset,
- }
-}
diff --git a/src/features/meetings/hooks/usePlaceSearch.ts b/src/features/meetings/hooks/usePlaceSearch.ts
new file mode 100644
index 0000000..6386dd2
--- /dev/null
+++ b/src/features/meetings/hooks/usePlaceSearch.ts
@@ -0,0 +1,168 @@
+/**
+ * @file usePlaceSearch.ts
+ * @description 장소 검색 모달의 상태 관리 훅
+ *
+ * SDK 로드, 지도 초기화, 장소 검색 API 등 모든 비동기 로직과 에러를
+ * 하나의 searchState로 추상화하여 UI가 선언적으로 상태를 수신할 수 있도록 합니다.
+ *
+ * searchState:
+ * - 'idle' 초기 화면 (검색 전)
+ * - 'searching' 검색 중 (리스트 스켈레톤 표시)
+ * - 'hasResults' 검색 결과 있음 (지도 + 리스트 표시)
+ * - 'noResults' 일치하는 결과 없음
+ * - 'error' SDK 로드 실패 또는 검색 API 오류
+ */
+
+import { useCallback, useEffect, useRef, useState } from 'react'
+
+import type { KakaoMap, KakaoPlace } from '@/features/kakaomap'
+import { useKakaoLoader, useKakaoPlaceSearch } from '@/features/kakaomap'
+
+export type PlaceSearchState = 'idle' | 'searching' | 'hasResults' | 'noResults' | 'error'
+
+type SelectedPlace = {
+ name: string
+ address: string
+ latitude: number
+ longitude: number
+}
+
+export type UsePlaceSearchOptions = {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ onSelectPlace: (place: SelectedPlace) => void
+}
+
+export function usePlaceSearch({ open, onOpenChange, onSelectPlace }: UsePlaceSearchOptions) {
+ const [sdkLoading, sdkError] = useKakaoLoader()
+
+ const [searchState, setSearchState] = useState('idle')
+ const [mapInstance, setMapInstance] = useState(null)
+ const [hoveredPlaceId, setHoveredPlaceId] = useState(null)
+
+ // 카카오 Map SDK는 마운트 시점의 컨테이너 크기로 지도를 초기화합니다.
+ // display:none 상태에서 마운트되면 크기가 0으로 계산되어 지도가 깨지므로,
+ // 첫 검색이 실행되어 지도 영역이 화면에 보이는 시점에 처음 마운트합니다.
+ const [hasBeenSearched, setHasBeenSearched] = useState(false)
+
+ const keywordRef = useRef(null)
+
+ const {
+ places,
+ error: searchError,
+ search,
+ reset,
+ } = useKakaoPlaceSearch({
+ onSearchSuccess: (results) => {
+ setSearchState(results.length > 0 ? 'hasResults' : 'noResults')
+ },
+ onSearchError: () => {
+ setSearchState('error')
+ },
+ })
+
+ // SDK 로드 실패 시 error 상태로 전환
+ const effectiveSearchState: PlaceSearchState = sdkError ? 'error' : searchState
+
+ // places 또는 mapInstance가 준비되면 지도 범위를 자동 조정
+ // (첫 검색 시 places가 먼저 오거나 mapInstance가 먼저 올 수 있으므로 둘 다 dep에 포함)
+ useEffect(() => {
+ if (!mapInstance || places.length === 0) return
+
+ const { kakao } = window
+ const bounds = new kakao.maps.LatLngBounds()
+ places.forEach((p) => bounds.extend(new kakao.maps.LatLng(Number(p.y), Number(p.x))))
+ mapInstance.setBounds(bounds)
+ }, [mapInstance, places])
+
+ const resetState = useCallback(() => {
+ setSearchState('idle')
+ setHoveredPlaceId(null)
+ setHasBeenSearched(false)
+ setMapInstance(null)
+ reset()
+ }, [reset])
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key !== 'Enter') return
+ // 한국어 등 IME 입력 중 composition 이벤트는 무시
+ if (e.nativeEvent.isComposing) return
+ e.preventDefault()
+
+ const keyword = keywordRef.current?.value.trim() ?? ''
+ if (!keyword) return
+
+ // SDK 로드 실패 상태에서는 검색 불가
+ if (sdkError) return
+
+ // SDK 아직 로드 중이라면 무시
+ if (sdkLoading) return
+
+ reset()
+ setHoveredPlaceId(null)
+ setSearchState('searching')
+ setHasBeenSearched(true)
+ search(keyword)
+ },
+ [sdkLoading, sdkError, reset, search]
+ )
+
+ const handlePlaceClick = useCallback(
+ (place: KakaoPlace) => {
+ if (mapInstance) {
+ mapInstance.setCenter(new window.kakao.maps.LatLng(Number(place.y), Number(place.x)))
+ }
+ onSelectPlace({
+ name: place.place_name,
+ address: place.road_address_name || place.address_name,
+ latitude: Number(place.y),
+ longitude: Number(place.x),
+ })
+ onOpenChange(false)
+ resetState()
+ },
+ [mapInstance, onSelectPlace, onOpenChange, resetState]
+ )
+
+ const handlePlaceFocus = useCallback(
+ (place: KakaoPlace) => {
+ if (!mapInstance) return
+ mapInstance.setLevel(4)
+ mapInstance.setCenter(new window.kakao.maps.LatLng(Number(place.y), Number(place.x)))
+ },
+ [mapInstance]
+ )
+
+ const handleClose = useCallback(() => {
+ onOpenChange(false)
+ resetState()
+ }, [onOpenChange, resetState])
+
+ // error 상태에서 노출할 메시지 — SDK 오류 우선
+ const errorMessage = sdkError?.message ?? searchError ?? '오류가 발생했습니다. 다시 시도해주세요.'
+
+ // Map 컴포넌트를 DOM에 마운트할지 여부
+ // error 상태는 인스턴스 보존이 불필요하므로 unmount (다음 검색 시 새로 초기화)
+ const isMapMounted = open && hasBeenSearched && effectiveSearchState !== 'error'
+
+ // 지도 영역을 화면에 표시할지 여부 (isMapMounted가 true일 때만 유의미)
+ // noResults에서는 Map 인스턴스를 유지한 채 CSS로만 숨김 → 재검색 시 재초기화 없이 재사용
+ const isMapVisible = effectiveSearchState === 'searching' || effectiveSearchState === 'hasResults'
+
+ return {
+ searchState: effectiveSearchState,
+ errorMessage,
+ places,
+ isMapMounted,
+ isMapVisible,
+ hoveredPlaceId,
+ keywordRef,
+ setMapInstance,
+ setHoveredPlaceId,
+ handleKeyDown,
+ handlePlaceClick,
+ handlePlaceFocus,
+ handleClose,
+ }
+}
diff --git a/src/features/meetings/index.ts b/src/features/meetings/index.ts
index 1e5b581..deccde9 100644
--- a/src/features/meetings/index.ts
+++ b/src/features/meetings/index.ts
@@ -11,12 +11,6 @@ export * from './lib'
export * from './meetings.api'
// Types
-export type {
- KakaoPlace,
- KakaoSearchMeta,
- KakaoSearchParams,
- KakaoSearchResponse,
-} from './kakaoMap.types'
export type {
ConfirmMeetingResponse,
CreateMeetingRequest,
diff --git a/src/features/meetings/kakaoMap.types.ts b/src/features/meetings/kakaoMap.types.ts
deleted file mode 100644
index 2f13f71..0000000
--- a/src/features/meetings/kakaoMap.types.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/**
- * @file kakao.types.ts
- * @description 카카오 로컬 API 관련 타입 정의
- * @note 외부 API 응답 스펙을 따르기 위해 snake_case 사용
- */
-
-/* eslint-disable @typescript-eslint/naming-convention */
-
-/**
- * 카카오 장소 검색 응답 문서 타입
- */
-export type KakaoPlace = {
- /** 장소명, 업체명 */
- place_name: string
- /** 전체 지번 주소 */
- address_name: string
- /** 전체 도로명 주소 */
- road_address_name: string
- /** X 좌표값, 경도(longitude) */
- x: string
- /** Y 좌표값, 위도(latitude) */
- y: string
- /** 장소 ID */
- id: string
- /** 카테고리 그룹 코드 */
- category_group_code: string
- /** 카테고리 그룹명 */
- category_group_name: string
- /** 카테고리 이름 */
- category_name: string
- /** 전화번호 */
- phone: string
- /** 장소 상세페이지 URL */
- place_url: string
- /** 중심좌표까지의 거리 (단, x,y 파라미터를 준 경우에만 존재) */
- distance?: string
-}
-
-/**
- * 카카오 장소 검색 API 응답 메타 정보
- */
-export type KakaoSearchMeta = {
- /** 검색된 문서 수 */
- total_count: number
- /** total_count 중 노출 가능 문서 수 */
- pageable_count: number
- /** 현재 페이지가 마지막 페이지인지 여부 */
- is_end: boolean
- /** 질의어의 지역 및 키워드 분석 정보 */
- same_name?: {
- /** 질의어에서 인식된 지역의 리스트 */
- region: string[]
- /** 질의어에서 지역 정보를 제외한 키워드 */
- keyword: string
- /** 인식된 지역 리스트 중, 현재 검색에 사용된 지역 정보 */
- selected_region: string
- }
-}
-
-/**
- * 카카오 장소 검색 API 응답 타입
- */
-export type KakaoSearchResponse = {
- /** 검색 결과 문서 리스트 */
- documents: KakaoPlace[]
- /** 응답 관련 정보 */
- meta: KakaoSearchMeta
-}
-
-/**
- * 카카오 장소 검색 API 요청 파라미터
- */
-export type KakaoSearchParams = {
- /** 검색을 원하는 질의어 (필수) */
- query: string
- /** 카테고리 그룹 코드 (선택) */
- category_group_code?: string
- /** 중심 좌표의 X 혹은 경도(longitude) */
- x?: string
- /** 중심 좌표의 Y 혹은 위도(latitude) */
- y?: string
- /** 중심 좌표부터의 반경거리. 미터(m) 단위 */
- radius?: number
- /** 결과 페이지 번호 (1~45, 기본값: 1) */
- page?: number
- /** 한 페이지에 보여질 문서의 개수 (1~15, 기본값: 15) */
- size?: number
- /** 결과 정렬 순서 (distance: 거리순, accuracy: 정확도순) */
- sort?: 'distance' | 'accuracy'
-}
diff --git a/src/features/meetings/loadKakaoSdk.ts b/src/features/meetings/loadKakaoSdk.ts
deleted file mode 100644
index 18b994a..0000000
--- a/src/features/meetings/loadKakaoSdk.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @file loadKakaoSdk.ts
- * @description 카카오 Maps SDK 싱글톤 로더
- *
- * - 앱 전체에서 SDK를 한 번만 로드 (중복 script 삽입 방지)
- * - 지도를 실제로 사용할 때만 로드 (불필요한 토큰/쿼터 소모 방지)
- * - autoload=false + maps.load() 콜백으로 초기화 완료 시점 보장
- */
-
-let kakaoSdkPromise: Promise | null = null
-
-export function loadKakaoSdk(): Promise {
- // 이미 초기화 완료된 경우
- if (window.kakao?.maps) {
- return Promise.resolve()
- }
-
- // 이미 로드 중인 경우 동일 Promise 반환 (중복 요청 방지)
- if (kakaoSdkPromise) {
- return kakaoSdkPromise
- }
-
- kakaoSdkPromise = new Promise((resolve, reject) => {
- const script = document.createElement('script')
- script.src = `https://dapi.kakao.com/v2/maps/sdk.js?appkey=${
- import.meta.env.VITE_KAKAO_MAP_KEY
- }&autoload=false&libraries=services`
- script.async = true
-
- script.onload = () => {
- try {
- window.kakao.maps.load(() => resolve())
- } catch (err) {
- kakaoSdkPromise = null
- const message = '카카오 지도 SDK 초기화에 실패했습니다.'
- console.error('[카카오 지도]', message, err)
- reject(new Error(message))
- }
- }
-
- script.onerror = () => {
- // 실패 시 Promise 초기화하여 재시도 가능하게
- kakaoSdkPromise = null
-
- // fetch로 실제 HTTP 상태 코드 확인 후 카카오 공식 상태 메시지 사용
- fetch(script.src)
- .then((res) => {
- if (res.ok) {
- const message =
- '카카오 지도 SDK 로드에 실패했습니다. 일시적인 네트워크 오류일 수 있으니 잠시 후 다시 시도해주세요.'
- console.error('[카카오 지도] SDK 로드 실패 (진단 불일치):', message)
- reject(new Error(message))
- return
- }
-
- const kakaoStatusMessages: Record = {
- 400: '잘못된 요청입니다. API에 필요한 필수 파라미터를 확인해주세요. (400 Bad Request)',
- 401: '인증 오류입니다. 앱키(VITE_KAKAO_MAP_KEY)가 올바른지 확인해주세요. (401 Unauthorized)',
- 403: '권한 오류입니다. 앱 등록 및 도메인 설정을 확인해주세요. (403 Forbidden)',
- 429: '쿼터를 초과했습니다. 정해진 사용량이나 초당 요청 한도를 초과했습니다. (429 Too Many Request)',
- 500: '카카오 서버 내부 오류입니다. 잠시 후 다시 시도해주세요. (500 Internal Server Error)',
- 502: '카카오 게이트웨이 오류입니다. 잠시 후 다시 시도해주세요. (502 Bad Gateway)',
- 503: '카카오 서비스 점검 중입니다. 잠시 후 다시 시도해주세요. (503 Service Unavailable)',
- }
- const message =
- kakaoStatusMessages[res.status] ??
- `카카오 지도 SDK 로드에 실패했습니다. (HTTP ${res.status})`
- const error = new Error(message)
- console.error('[카카오 지도] SDK 로드 실패:', message)
- reject(error)
- })
- .catch(() => {
- const message = '카카오 지도 SDK를 로드할 수 없습니다. 네트워크 연결을 확인해주세요.'
- console.error('[카카오 지도] SDK 로드 실패 (네트워크 오류):', message)
- reject(new Error(message))
- })
- }
-
- document.head.appendChild(script)
- })
-
- return kakaoSdkPromise
-}
diff --git a/src/features/topics/topics.api.ts b/src/features/topics/topics.api.ts
index 926758d..3770397 100644
--- a/src/features/topics/topics.api.ts
+++ b/src/features/topics/topics.api.ts
@@ -230,7 +230,7 @@ export const confirmTopics = async (
// 🚧 임시: 로그인 기능 개발 전까지 목데이터 사용
// TODO: 로그인 완료 후 아래 주석을 해제하고 목데이터 로직 제거
- if (USE_MOCK_DATA) {
+ if (USE_MOCK) {
// 실제 API 호출을 시뮬레이션하기 위한 지연
await new Promise((resolve) => setTimeout(resolve, 500))
return getMockConfirmTopics(meetingId, topicIds)
diff --git a/src/shared/ui/SearchField.tsx b/src/shared/ui/SearchField.tsx
index 390527a..7078b7e 100644
--- a/src/shared/ui/SearchField.tsx
+++ b/src/shared/ui/SearchField.tsx
@@ -15,31 +15,35 @@ type SearchFieldProps = Omit, 'type'>
* setQuery(e.target.value)} />
* ```
*/
-function SearchField({ className, disabled, ...props }: SearchFieldProps) {
- return (
-
-
-
(
+ ({ className, disabled, ...props }, ref) => {
+ return (
+
-
- )
-}
+ >
+
+
+
+ )
+ }
+)
+SearchField.displayName = 'SearchField'
export { SearchField }