diff --git a/src/app/router/Router.tsx b/src/app/router/Router.tsx index c0c79df7..dc9bd3f1 100644 --- a/src/app/router/Router.tsx +++ b/src/app/router/Router.tsx @@ -7,7 +7,7 @@ import { AroundSearch, TourList, TourSearch, - TourSingleInfo, + TourShare, } from '@/app/router'; import { Home } from '@/pages/home'; import { GeoTrip } from '@/pages/tour/geotrip'; @@ -28,8 +28,8 @@ export default function Router() { } /> }> - } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/src/app/router/lazyRoutes.ts b/src/app/router/lazyRoutes.ts index d20ea25e..4112a1c3 100644 --- a/src/app/router/lazyRoutes.ts +++ b/src/app/router/lazyRoutes.ts @@ -9,6 +9,4 @@ export const AroundSearch = lazy( ); export const TourList = lazy(() => import('@/pages/tour/tourList/TourList')); export const TourSearch = lazy(() => import('@/pages/tour/search/TourSearch')); -export const TourSingleInfo = lazy( - () => import('@/pages/tour/geotrip/SingleTrip'), -); +export const TourShare = lazy(() => import('@/pages/tour/geotrip/Share')); diff --git a/src/features/aroundTourist/ui/GeoAroundTouristMap.tsx b/src/features/aroundTourist/ui/GeoAroundTouristMap.tsx index 46287e71..e7a5daa5 100644 --- a/src/features/aroundTourist/ui/GeoAroundTouristMap.tsx +++ b/src/features/aroundTourist/ui/GeoAroundTouristMap.tsx @@ -1,4 +1,4 @@ -import { useMemo, useRef, useState } from 'react'; +import { useMemo, useRef } from 'react'; import { useQuery } from '@tanstack/react-query'; import { Map } from 'react-kakao-maps-sdk'; @@ -10,26 +10,25 @@ import { NearbyTouristAttractionPinPoint, MiddleContent, } from '@/features/aroundTourist'; -import { TouristContentsTypeFilter } from '@/shared'; +import { TouristContentsTypeFilter, useLocalStorage } from '@/shared'; -import type { AroundContentTypeId, TourItem } from '@/entities/tour'; +import type { TourItem } from '@/entities/tour'; import type { GeoTripLocation } from '@/shared'; +import type { TourInjected } from '@/features/tour'; interface GeoAroundTouristMapProps { location: GeoTripLocation; contentId: string; - tourContentTypeId: AroundContentTypeId; } -function GeoAroundTouristMap({ - location, - tourContentTypeId, -}: GeoAroundTouristMapProps) { - const [selectedContentTypeId, setSelectedContentTypeId] = - useState(tourContentTypeId); +function GeoAroundTouristMap({ location }: GeoAroundTouristMapProps) { + const [tourFilter, setTourFilter] = useLocalStorage('tourInfo', { + distance: '20000', + contentTypeId: '12', + } as TourInjected); const { data: aroundTouristObjects = [] } = useQuery( - aroundTouristQueries.list(location, selectedContentTypeId), + aroundTouristQueries.list(location, tourFilter.contentTypeId), ); const middleTouristRef = useRef(null); @@ -53,8 +52,8 @@ function GeoAroundTouristMap({
diff --git a/src/features/auth/ui/AuthButtonContainer.tsx b/src/features/auth/ui/AuthButtonContainer.tsx index 59442a78..38fb5c2f 100644 --- a/src/features/auth/ui/AuthButtonContainer.tsx +++ b/src/features/auth/ui/AuthButtonContainer.tsx @@ -10,7 +10,7 @@ export default function AuthButtonContainer() { mutation.mutate(); }; const handleGuestLogin = () => { - navigate('/tour/geo-trip?distance=20000&tour-type=12'); + navigate('/tour/geo'); }; return ( diff --git a/src/features/map/lib/withAroundMapParams.tsx b/src/features/map/lib/withAroundMapParams.tsx index 243994dc..09b7b6de 100644 --- a/src/features/map/lib/withAroundMapParams.tsx +++ b/src/features/map/lib/withAroundMapParams.tsx @@ -19,16 +19,13 @@ export default function withAroundMapParams

( const mapx = searchParams.get('lng'); const mapy = searchParams.get('lat'); const contentId = searchParams.get('contentId'); - const tourContentTypeId = searchParams.get( - 'contentTypeId', - ) as AroundContentTypeId; const location: GeoTripLocation = { lat: mapy ? Number(mapy) : 0, lng: mapx ? Number(mapx) : 0, }; - if (!location || !contentId || !tourContentTypeId) { + if (!location || !contentId) { return (

필요한 정보가 부족합니다. 위치, 콘텐츠 ID, 관광지 타입을 확인해주세요. @@ -41,7 +38,6 @@ export default function withAroundMapParams

( {...(props as P)} location={location} contentId={contentId} - tourContentTypeId={tourContentTypeId} /> ); }; diff --git a/src/features/shared/index.ts b/src/features/shared/index.ts new file mode 100644 index 00000000..03a8d531 --- /dev/null +++ b/src/features/shared/index.ts @@ -0,0 +1,2 @@ +export * from './lib'; +export * from './ui'; diff --git a/src/features/shared/lib/index.ts b/src/features/shared/lib/index.ts new file mode 100644 index 00000000..2de0abe5 --- /dev/null +++ b/src/features/shared/lib/index.ts @@ -0,0 +1 @@ +export { default as useKakaoShare } from './useKakaoShare'; diff --git a/src/features/tour/ui/single/Single.tsx b/src/features/shared/ui/ShareTrip.tsx similarity index 96% rename from src/features/tour/ui/single/Single.tsx rename to src/features/shared/ui/ShareTrip.tsx index 373a0e74..b901ce25 100644 --- a/src/features/tour/ui/single/Single.tsx +++ b/src/features/shared/ui/ShareTrip.tsx @@ -5,7 +5,7 @@ import { tourQueries, type TourItem } from '@/entities/tour'; import { LoadingSpinner } from '@/shared/ui'; import { TourSlide } from '@/features/tourShort'; -export default function Single() { +export default function ShareTrip() { const { contentId } = useParams(); const navigate = useNavigate(); diff --git a/src/features/shared/ui/SharedButtonContainer.tsx b/src/features/shared/ui/SharedButtonContainer.tsx index 23d3998a..4d32e447 100644 --- a/src/features/shared/ui/SharedButtonContainer.tsx +++ b/src/features/shared/ui/SharedButtonContainer.tsx @@ -9,7 +9,7 @@ interface SharedButtonContainerProps { export default function SharedButtonContainer({ contentId, }: SharedButtonContainerProps) { - const link = `${window.location.origin}/tour/single/${contentId}`; + const link = `${window.location.origin}/tour/share/${contentId}`; const { shareKakao } = useKakaoShare({ contentId, link }); return ( diff --git a/src/features/shared/ui/index.ts b/src/features/shared/ui/index.ts new file mode 100644 index 00000000..a36efd3f --- /dev/null +++ b/src/features/shared/ui/index.ts @@ -0,0 +1,2 @@ +export { default as ShareTrip } from './ShareTrip'; +export { default as SharedButtonContainer } from './SharedButtonContainer'; diff --git a/src/features/tour/lib/withGeoTripParams.tsx b/src/features/tour/lib/withGeoTripParams.tsx index 05758447..be41772a 100644 --- a/src/features/tour/lib/withGeoTripParams.tsx +++ b/src/features/tour/lib/withGeoTripParams.tsx @@ -1,36 +1,32 @@ -import { useSearchParams } from 'react-router-dom'; - import { isValidTourType } from '@/features/map'; -import type { AroundContentTypeId } from '@/entities/tour'; -interface InjectedProps { - distance: string; - tourContentTypeId: AroundContentTypeId; -} +import { useLocalStorage } from '@/shared'; +import type { TourInjected } from '../types'; -export function withGeoTripParams

( +export function withGeoTripParams

( WrappedComponent: React.ComponentType

, ) { - return function GeoTripWrapper(props: Omit) { - const [searchParams] = useSearchParams(); - const distance = searchParams.get('distance'); - const tourContentTypeId = searchParams.get('tour-type'); + return function GeoTripWrapper(props: Omit) { + const [tourInfo] = useLocalStorage('tourInfo', { + distance: '20000', + contentTypeId: '12', + }); - if (!distance || !tourContentTypeId) { + if (!tourInfo.distance || !tourInfo.contentTypeId) { throw new Error( '필요한 정보가 부족합니다. 거리와 관광지 타입을 확인해주세요.', ); } - if (!isValidTourType(tourContentTypeId)) { + if (!isValidTourType(tourInfo.contentTypeId)) { throw new Error('잘못된 관광 타입입니다.'); } return ( ); }; diff --git a/src/features/tour/types.ts b/src/features/tour/types.ts index f17c25ed..d0b130f6 100644 --- a/src/features/tour/types.ts +++ b/src/features/tour/types.ts @@ -33,3 +33,8 @@ export type TourDetailImage = { originimgurl?: string; serialnum: string; }; + +export type TourInjected = { + distance: string; + contentTypeId: AroundContentTypeId; +}; diff --git a/src/features/tourDetail/ui/TourOverview.tsx b/src/features/tourDetail/ui/TourOverview.tsx index 41cbd17f..997483c1 100644 --- a/src/features/tourDetail/ui/TourOverview.tsx +++ b/src/features/tourDetail/ui/TourOverview.tsx @@ -1,10 +1,9 @@ import { useSuspenseQuery } from '@tanstack/react-query'; -import { commonSVG } from '@/assets'; - import { BookmarkButtonContainer } from '@/features/bookmark'; import { tourQueries } from '@/entities/tour'; -import { TourTypeBadge, DistanceTimeInfo, getCopyClipBoard } from '@/shared'; +import { TourTypeBadge, DistanceTimeInfo } from '@/shared'; +import { SharedButtonContainer } from '@/features/shared'; interface TourCardProps { distance: string; @@ -37,10 +36,8 @@ export default function TourOverview({

- getCopyClipBoard(window.location.href)} - /> + +
diff --git a/src/features/tourFilter/hook/index.ts b/src/features/tourFilter/hook/index.ts deleted file mode 100644 index d8254b45..00000000 --- a/src/features/tourFilter/hook/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useTourFilterQuery'; diff --git a/src/features/tourFilter/hook/useTourFilterQuery.ts b/src/features/tourFilter/hook/useTourFilterQuery.ts deleted file mode 100644 index 139f631c..00000000 --- a/src/features/tourFilter/hook/useTourFilterQuery.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useSearchParams } from 'react-router-dom'; - -import { isValidTourType } from '@/features/map'; -import { isValidDistance } from '@/features/tourFilter'; - -import type { Distance } from '@/features/tourFilter'; -import type { AroundContentTypeId } from '@/entities/tour'; - -type UpdateType = { - tourType: AroundContentTypeId; - distance: Distance; -}; - -export const useTourFilterQuery = () => { - const [searchParams, setSearchParams] = useSearchParams(); - - const updateQuery = ({ tourType, distance }: UpdateType) => { - const nextParams = new URLSearchParams(searchParams); - nextParams.set('tour-type', tourType); - nextParams.set('distance', String(distance * 1000)); - // nextParams.set('sort', sortOption); - nextParams.delete('slide-index'); - nextParams.delete('page-param'); - setSearchParams(nextParams, { replace: true }); - }; - - const getQuery = () => { - const tourType = searchParams.get('tour-type'); - const distance = Number(searchParams.get('distance')) / 1000; - - if (isValidTourType(tourType) && isValidDistance(distance)) { - return { tourType, distance }; - } - }; - - return { searchParams, updateQuery, getQuery }; -}; diff --git a/src/features/tourFilter/index.ts b/src/features/tourFilter/index.ts index 40e2e5de..593a8092 100644 --- a/src/features/tourFilter/index.ts +++ b/src/features/tourFilter/index.ts @@ -1,4 +1,3 @@ export * from './ui'; export * from './const'; export * from './type'; -export * from './hook'; diff --git a/src/features/tourFilter/ui/DistanceSlider.tsx b/src/features/tourFilter/ui/DistanceSlider.tsx index c0eef5f4..ab017f3f 100644 --- a/src/features/tourFilter/ui/DistanceSlider.tsx +++ b/src/features/tourFilter/ui/DistanceSlider.tsx @@ -1,28 +1,37 @@ -import type { Dispatch } from 'react'; -import type { Distance } from '@/features/tourFilter'; +import type { TourInjected } from '@/features/tour/types'; interface DistanceSliderProps { - distance: number; - setDistance: Dispatch>; + distance: string; + setDistance: ( + value: TourInjected | ((val: TourInjected) => TourInjected), + ) => void; } export default function DistanceSlider({ distance, setDistance, }: DistanceSliderProps) { + const handleDistanceChange = (e: React.ChangeEvent) => { + setDistance(prev => ({ + ...prev, + distance: e.target.value, + })); + }; + return ( <>
1km + {distance}m 20km
setDistance(Number(e.target.value) as Distance)} + onChange={handleDistanceChange} className="w-full" /> diff --git a/src/features/tourFilter/ui/TourFilterSidebar.tsx b/src/features/tourFilter/ui/TourFilterSidebar.tsx index 0044dec6..5f8c77f2 100644 --- a/src/features/tourFilter/ui/TourFilterSidebar.tsx +++ b/src/features/tourFilter/ui/TourFilterSidebar.tsx @@ -1,14 +1,9 @@ -import { useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; -import { - DistanceSlider, - SortOptions, - useTourFilterQuery, -} from '@/features/tourFilter'; -import { TouristContentsTypeFilter } from '@/shared'; - -import type { SortOption } from '@/features/tourFilter'; +import { DistanceSlider } from '@/features/tourFilter'; +import { TouristContentsTypeFilter, useLocalStorage } from '@/shared'; +import type { TourInjected } from '@/features/tour/types'; +import { useState } from 'react'; interface BottomSheetProps { onClose: () => void; @@ -19,19 +14,31 @@ export default function TourFilterSidebar({ onClose, isOpen, }: BottomSheetProps) { - const { getQuery, updateQuery } = useTourFilterQuery(); + const [tourFilter, setTourFilter] = useLocalStorage('tourInfo', { + distance: '20000', + contentTypeId: '12', + } as TourInjected); - const [aroundContentTypeId, setAroundContentTypeId] = useState( - getQuery()?.tourType ?? '12', - ); - const [distance, setDistance] = useState(getQuery()?.distance || 1); - const [sortOption, setSortOption] = useState('distance'); + const [currentTourFilter, setCurrentTourFilter] = useState({ + distance: tourFilter.distance, + contentTypeId: tourFilter.contentTypeId, + }); const handleSubmit = () => { - updateQuery({ tourType: aroundContentTypeId, distance }); + setTourFilter({ + distance: currentTourFilter.distance, + contentTypeId: currentTourFilter.contentTypeId, + }); onClose(); }; + const handleReset = () => { + setCurrentTourFilter({ + distance: '20000', + contentTypeId: '12', + }); + }; + return ( {isOpen && ( @@ -58,28 +65,25 @@ export default function TourFilterSidebar({ 관광 타입
- -
-
- -