diff --git a/src/pages/join/components/LocationStep.jsx b/src/pages/join/components/LocationStep.jsx index 102a8a7..0a7022b 100644 --- a/src/pages/join/components/LocationStep.jsx +++ b/src/pages/join/components/LocationStep.jsx @@ -36,13 +36,12 @@ const LocationStep = ({ onNext, onBack }) => { const showNotSeoulMessage = checked; return ( -
- +
+
+ +

어느 동네에 사세요?

diff --git a/src/pages/join/components/NameStep.jsx b/src/pages/join/components/NameStep.jsx index ab0e1b6..43e675b 100644 --- a/src/pages/join/components/NameStep.jsx +++ b/src/pages/join/components/NameStep.jsx @@ -21,16 +21,16 @@ const NameStep = ({ onNext, onBack }) => { onSubmit={handleSubmit} className="flex flex-col justify-start mt-48 bg-white h-screen overflow-auto relative" > - +

+ +
-

이름을 입력해주세요.

+

+ 이름을 입력해주세요. +

투명한 리뷰에 사용됩니다.

@@ -79,9 +79,7 @@ const NameStep = ({ onNext, onBack }) => { type="submit" disabled={!name.trim()} className={`w-full py-6 text-center text-b1 font-semibold ${ - name.trim() - ? "bg-primary-8 text-white" - : "bg-gray-4 text-white" + name.trim() ? "bg-primary-8 text-white" : "bg-gray-4 text-white" }`} > 확인 diff --git a/src/pages/join/components/OwnerStep.jsx b/src/pages/join/components/OwnerStep.jsx index b11413a..7d76c7f 100644 --- a/src/pages/join/components/OwnerStep.jsx +++ b/src/pages/join/components/OwnerStep.jsx @@ -17,13 +17,12 @@ const OwnerStep = ({ onNext, onBack }) => { }; return ( -
- +
+
+ +

사회적기업의 사장님이신가요? diff --git a/src/pages/join/components/ProfileImageStep.jsx b/src/pages/join/components/ProfileImageStep.jsx index 92ad3d4..0873fb1 100644 --- a/src/pages/join/components/ProfileImageStep.jsx +++ b/src/pages/join/components/ProfileImageStep.jsx @@ -25,13 +25,11 @@ const ProfileImageStep = ({ onNext, onBack }) => { return (
- +
+ +

프로필사진을 설정해주세요. diff --git a/src/pages/map/components/PlaceBottomSheet.jsx b/src/pages/map/components/PlaceBottomSheet.jsx index 95d6491..aa7e5da 100644 --- a/src/pages/map/components/PlaceBottomSheet.jsx +++ b/src/pages/map/components/PlaceBottomSheet.jsx @@ -7,7 +7,13 @@ import ReviewList from "@/pages/map/components/review/ReviewList"; import useUIStore from "@/store/uiStore"; import { usePaymentStore } from "@/store/paymentStore"; -const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => { +const PlaceBottomSheet = ({ + place, + onClose, + onToggleLike, + onExpandChange, + onHeightChange, +}) => { const [shouldNavigateToReview, setShouldNavigateToReview] = useState(false); const setCompanyId = usePaymentStore((s) => s.setCompanyId); @@ -59,7 +65,8 @@ const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => { useEffect(() => { controls.start({ height: MIN_HEIGHT }); setIsExpanded(false); - }, [controls]); + onHeightChange?.(MIN_HEIGHT); + }, [controls, onHeightChange]); const handleTouchStart = (e) => { if (!isMobile) return; @@ -89,11 +96,13 @@ const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => { setIsExpanded(true); } else if (delta > 100) { controls.start({ height: 0 }).then(() => { + onHeightChange?.(0); onClose?.(); }); } else { controls.start({ height: MIN_HEIGHT }); setIsExpanded(false); + onHeightChange?.(MIN_HEIGHT); } }; @@ -101,6 +110,7 @@ const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => { if (!isMobile && !isExpanded) { controls.start({ height: MAX_HEIGHT.current }); setIsExpanded(true); + onHeightChange?.(MAX_HEIGHT.current); } }; @@ -170,8 +180,8 @@ const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => { turnOnCamera={turnOnCamera} onCloseCamera={() => setTurnOnCamera(false)} onCaptureSuccess={(data) => { - setCompanyInfo(data); // 즉시 로컬 상태에 저장 - setShowConfirm(true); // 그다음 Confirm 렌더링 + setCompanyInfo(data); + setShowConfirm(true); }} /> )} diff --git a/src/pages/map/components/PlaceContent.jsx b/src/pages/map/components/PlaceContent.jsx index 6b5cb69..7c033d2 100644 --- a/src/pages/map/components/PlaceContent.jsx +++ b/src/pages/map/components/PlaceContent.jsx @@ -24,6 +24,7 @@ import { useState } from "react"; const PlaceContent = ({ place, onToggleLike, showMapLink = true }) => { const userCoords = useUserCoords(); const [showToast, setShowToast] = useState(false); + const [isLiking, setIsLiking] = useState(false); const handleRouteClick = (e) => { e.preventDefault(); @@ -76,6 +77,20 @@ const PlaceContent = ({ place, onToggleLike, showMapLink = true }) => { } }; + const handleLikeClick = async (e) => { + e.stopPropagation(); + if (isLiking) return; + + setIsLiking(true); + try { + await onToggleLike?.(place.id); + } catch (err) { + console.error("좋아요 처리 실패:", err); + } finally { + setIsLiking(false); + } + }; + const fireIcons = { 10: IcFire10, 20: IcFire20, @@ -157,10 +172,7 @@ const PlaceContent = ({ place, onToggleLike, showMapLink = true }) => { )} +

+ )} + {selectedPlace && isBottomSheetVisible && ( setIsBottomSheetVisible(false)} onExpandChange={setIsBottomSheetExpanded} onToggleLike={handleToggleLike} + onHeightChange={setBottomSheetHeight} /> )}

diff --git a/src/pages/search/components/MapViewer.jsx b/src/pages/search/components/MapViewer.jsx new file mode 100644 index 0000000..535ca61 --- /dev/null +++ b/src/pages/search/components/MapViewer.jsx @@ -0,0 +1,38 @@ +import { useRef } from "react"; +import useMapViewer from "../hooks/useMapViewer"; + +const MapViewer = ({ + places, + onMarkerClick, + userCoords, + moveToCurrentLocation, + onMoveComplete, + resetMap, + center, + markerPosition, + zoom = 11, + selectedPlace, + showOnlyLiked, + disableAutoUserPan = false, +}) => { + const mapRef = useRef(null); + + useMapViewer({ + mapRef, + places, + onMarkerClick, + userCoords, + moveToCurrentLocation, + onMoveComplete, + resetMap, + center, + markerPosition, + zoom, + selectedPlace, + disableAutoUserPan, + }); + + return
; +}; + +export default MapViewer; diff --git a/src/pages/search/hooks/useMapViewer.js b/src/pages/search/hooks/useMapViewer.js new file mode 100644 index 0000000..773c8bc --- /dev/null +++ b/src/pages/search/hooks/useMapViewer.js @@ -0,0 +1,226 @@ +import { useEffect, useRef, useCallback, useState } from "react"; +import { loadNaverMapScript } from "@pages/map/utils/loadMapScript"; +import { + createMarkerIcon, + createUserMarkerIcon, +} from "@pages/map/utils/mapHelpers"; + +const useMapViewer = ({ + mapRef, + places, + onMarkerClick, + userCoords, + moveToCurrentLocation, + onMoveComplete, + resetMap, + center, + markerPosition, + zoom = 11, + selectedPlace, + showOnlyLiked, + disableAutoUserPan, +}) => { + const mapInstance = useRef(null); + const userMarkerRef = useRef(null); + const markersRef = useRef({}); + const hasAnimatedRef = useRef(false); + const [isMapInitialized, setIsMapInitialized] = useState(false); + + const handleMarkerClick = useCallback( + (place) => { + Object.values(markersRef.current).forEach((marker) => { + marker.setIcon(createMarkerIcon(false, false)); + }); + + const clickedMarker = markersRef.current[place.id]; + if (clickedMarker) { + clickedMarker.setIcon( + createMarkerIcon(true, showOnlyLiked ? place.liked : false), + ); + } + + onMarkerClick?.(place); + }, + [onMarkerClick, showOnlyLiked], + ); + + useEffect(() => { + if (isMapInitialized || (!userCoords && !center)) return; + + loadNaverMapScript().then(() => { + const mapCenter = center + ? new window.naver.maps.LatLng(center.lat, center.lng) + : new window.naver.maps.LatLng(37.5665, 126.978); + + mapInstance.current = new window.naver.maps.Map(mapRef.current, { + center: mapCenter, + zoom, + }); + + setIsMapInitialized(true); + + places.forEach((place) => { + if (!place.coords?.lat || !place.coords?.lng) return; + + const isHighlighted = + place.isSearchResult || + (selectedPlace && selectedPlace.id === place.id); + + const marker = new window.naver.maps.Marker({ + position: new window.naver.maps.LatLng( + place.coords.lat, + place.coords.lng, + ), + map: mapInstance.current, + icon: createMarkerIcon(isHighlighted, place.liked), + }); + + markersRef.current[place.id] = marker; + + window.naver.maps.Event.addListener(marker, "click", () => { + handleMarkerClick(place); + console.log("marker"); + }); + }); + + if (markerPosition) { + new window.naver.maps.Marker({ + position: new window.naver.maps.LatLng( + markerPosition.lat, + markerPosition.lng, + ), + map: mapInstance.current, + icon: createMarkerIcon(true), + }); + } + }); + }, [ + userCoords, + places, + center, + zoom, + selectedPlace, + markerPosition, + handleMarkerClick, + isMapInitialized, + mapRef, + showOnlyLiked, + ]); + + useEffect(() => { + if ( + userCoords && + mapInstance.current && + !hasAnimatedRef.current && + isMapInitialized && + !disableAutoUserPan + ) { + hasAnimatedRef.current = true; + + const target = new window.naver.maps.LatLng( + userCoords.lat, + userCoords.lng, + ); + mapInstance.current.panTo(target); + + const zoomTimeout = setTimeout(() => { + mapInstance.current.setZoom(17, true); + if (userMarkerRef.current) { + userMarkerRef.current.setMap(null); + } + + userMarkerRef.current = new window.naver.maps.Marker({ + position: target, + map: mapInstance.current, + icon: createUserMarkerIcon(), + }); + }, 1800); + + return () => clearTimeout(zoomTimeout); + } + }, [userCoords, isMapInitialized, disableAutoUserPan]); + + useEffect(() => { + if (moveToCurrentLocation && userCoords && mapInstance.current) { + const newCenter = new window.naver.maps.LatLng( + userCoords.lat, + userCoords.lng, + ); + mapInstance.current.setCenter(newCenter); + mapInstance.current.setZoom(17); + + if (userMarkerRef.current) userMarkerRef.current.setMap(null); + + userMarkerRef.current = new window.naver.maps.Marker({ + position: newCenter, + map: mapInstance.current, + icon: createUserMarkerIcon(), + }); + + onMoveComplete?.(); + } + }, [moveToCurrentLocation, userCoords, onMoveComplete]); + + useEffect(() => { + if (resetMap && mapInstance.current) { + mapInstance.current.setCenter( + new window.naver.maps.LatLng(37.5665, 126.978), + ); + mapInstance.current.setZoom(11.5); + + if (userMarkerRef.current) { + userMarkerRef.current.setMap(null); + userMarkerRef.current = null; + } + } + }, [resetMap]); + + useEffect(() => { + if (!mapInstance.current || !isMapInitialized) return; + + const currentMarkerIds = new Set(Object.keys(markersRef.current)); + const newPlaceIds = new Set(places.map((p) => String(p.id))); + + currentMarkerIds.forEach((id) => { + if (!newPlaceIds.has(id)) { + markersRef.current[id].setMap(null); + delete markersRef.current[id]; + } + }); + + places.forEach((place) => { + const isHighlighted = + place.isSearchResult || + (selectedPlace && selectedPlace.id === place.id); + + const marker = markersRef.current[place.id]; + + if (!marker) { + const newMarker = new window.naver.maps.Marker({ + position: new window.naver.maps.LatLng( + place.coords.lat, + place.coords.lng, + ), + map: mapInstance.current, + icon: createMarkerIcon(isHighlighted, place.liked), + }); + + markersRef.current[place.id] = newMarker; + + window.naver.maps.Event.addListener(newMarker, "click", () => + handleMarkerClick(place), + ); + } else { + marker.setIcon(createMarkerIcon(isHighlighted, place.liked)); + } + }); + }, [ + places, + selectedPlace, + isMapInitialized, + handleMarkerClick, + showOnlyLiked, + ]); +}; + +export default useMapViewer;