From bdf5012a07f1c2679d1f129af1d19cf60f496e5a Mon Sep 17 00:00:00 2001 From: zzzryt Date: Sun, 7 Dec 2025 15:59:02 +0900 Subject: [PATCH 1/7] Empty-Commit From 0cce59619a7566295d829667cb1f5eb5dc804eda Mon Sep 17 00:00:00 2001 From: zzzryt Date: Sun, 7 Dec 2025 22:26:27 +0900 Subject: [PATCH 2/7] =?UTF-8?q?(#197):=20tour=20filter=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4=ED=86=A0=EB=A6=AC?= =?UTF-8?q?=EC=A7=80=EC=97=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router/Router.tsx | 1 + .../aroundTourist/ui/GeoAroundTouristMap.tsx | 25 ++++---- src/features/map/lib/withAroundMapParams.tsx | 6 +- src/features/tour/lib/withGeoTripParams.tsx | 28 ++++----- src/features/tour/types.ts | 5 ++ src/features/tourFilter/hook/index.ts | 1 - .../tourFilter/hook/useTourFilterQuery.ts | 37 ------------ src/features/tourFilter/index.ts | 1 - src/features/tourFilter/ui/DistanceSlider.tsx | 25 +++++--- .../tourFilter/ui/TourFilterSidebar.tsx | 60 ++++++++++--------- .../ui/TouristFilterQueryUpdater.tsx | 24 +++----- .../tourList/ui/TourListContainer.tsx | 14 +---- .../tourShort/ui/TourSwiperContainer.tsx | 13 +--- src/pages/tour/geotrip/GeoTrip.tsx | 13 ++-- src/shared/ui/TouristContentsTypeFilter.tsx | 12 +++- src/widgets/bottomNavigationBar/index.tsx | 6 +- src/widgets/bottomNavigationBar/utils.ts | 11 +--- 17 files changed, 117 insertions(+), 165 deletions(-) delete mode 100644 src/features/tourFilter/hook/index.ts delete mode 100644 src/features/tourFilter/hook/useTourFilterQuery.ts diff --git a/src/app/router/Router.tsx b/src/app/router/Router.tsx index c0c79df7..41a51031 100644 --- a/src/app/router/Router.tsx +++ b/src/app/router/Router.tsx @@ -28,6 +28,7 @@ export default function Router() { } /> }> + } /> } /> } /> } /> 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/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/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/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({ 관광 타입
- -
-
- -
-
diff --git a/src/shared/ui/TouristContentsTypeFilter.tsx b/src/shared/ui/TouristContentsTypeFilter.tsx index 2541dada..48f4dc7e 100644 --- a/src/shared/ui/TouristContentsTypeFilter.tsx +++ b/src/shared/ui/TouristContentsTypeFilter.tsx @@ -6,10 +6,13 @@ import clsx from 'clsx'; import { markerList } from '@/features/aroundTourist'; import type { AroundContentTypeId } from '@/entities/tour'; +import type { TourInjected } from '@/features/tour'; interface TouristContentsTypeFilterProps { contentTypeId: AroundContentTypeId; - setContentTypeId: React.Dispatch>; + setContentTypeId: ( + value: TourInjected | ((val: TourInjected) => TourInjected), + ) => void; } export default function TouristContentsTypeFilter({ contentTypeId, @@ -37,7 +40,12 @@ export default function TouristContentsTypeFilter({ diff --git a/src/widgets/bottomNavigationBar/index.tsx b/src/widgets/bottomNavigationBar/index.tsx index b5ac9fb5..37b24bde 100644 --- a/src/widgets/bottomNavigationBar/index.tsx +++ b/src/widgets/bottomNavigationBar/index.tsx @@ -1,14 +1,12 @@ -import { useLocation, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import clsx from 'clsx'; import { createNavItems } from '@/widgets/bottomNavigationBar/utils'; export function BottomNavigationBar() { - const location = useLocation(); const navigate = useNavigate(); - const currentParams = new URLSearchParams(location.search); - const navItems = createNavItems({ currentParams, navigate }); + const navItems = createNavItems({ navigate }); const getIconClass = (path: string) => clsx({ 'text-black': location.pathname !== path, diff --git a/src/widgets/bottomNavigationBar/utils.ts b/src/widgets/bottomNavigationBar/utils.ts index af6b78e4..fd4332e5 100644 --- a/src/widgets/bottomNavigationBar/utils.ts +++ b/src/widgets/bottomNavigationBar/utils.ts @@ -7,19 +7,12 @@ import { } from '@/assets/common'; type createNavItemsParams = { - currentParams: URLSearchParams; navigate: (to: To, options?: NavigateOptions) => void | Promise; }; -export const createNavItems = ({ - currentParams, - navigate, -}: createNavItemsParams) => { - const paramsString = currentParams.toString(); - +export const createNavItems = ({ navigate }: createNavItemsParams) => { const navigateTo = (path: string) => { - const destination = paramsString ? `${path}?${paramsString}` : path; - navigate(destination, { replace: true }); + navigate(path, { replace: true }); }; return [ From a1f814fe4866fda6f81fd8efbe87153b577c3f3a Mon Sep 17 00:00:00 2001 From: zzzryt Date: Tue, 9 Dec 2025 15:29:42 +0900 Subject: [PATCH 3/7] =?UTF-8?q?(#197):=20share=EC=99=80geo=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router/Router.tsx | 7 +++---- src/app/router/lazyRoutes.ts | 4 +--- src/features/shared/index.ts | 2 ++ src/features/shared/lib/index.ts | 1 + .../single/Single.tsx => shared/ui/ShareTrip.tsx} | 2 +- src/features/shared/ui/SharedButtonContainer.tsx | 2 +- src/features/shared/ui/index.ts | 2 ++ .../tour/geotrip/{SingleTrip.tsx => Share.tsx} | 15 +++++++++------ src/pages/tour/geotrip/index.ts | 2 +- src/widgets/bottomNavigationBar/utils.ts | 4 ++-- 10 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 src/features/shared/index.ts create mode 100644 src/features/shared/lib/index.ts rename src/features/{tour/ui/single/Single.tsx => shared/ui/ShareTrip.tsx} (96%) create mode 100644 src/features/shared/ui/index.ts rename src/pages/tour/geotrip/{SingleTrip.tsx => Share.tsx} (80%) diff --git a/src/app/router/Router.tsx b/src/app/router/Router.tsx index 41a51031..3f6e94f2 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,9 +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/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/pages/tour/geotrip/SingleTrip.tsx b/src/pages/tour/geotrip/Share.tsx similarity index 80% rename from src/pages/tour/geotrip/SingleTrip.tsx rename to src/pages/tour/geotrip/Share.tsx index 867cc7a5..1de4f26c 100644 --- a/src/pages/tour/geotrip/SingleTrip.tsx +++ b/src/pages/tour/geotrip/Share.tsx @@ -1,24 +1,27 @@ -import SingleTrip from '@/features/tour/ui/single/Single'; -import { LoadingSpinner, QueryErrorBoundary } from '@/shared'; -import { BottomNavigationBar, Seo } from '@/widgets'; import { Suspense } from 'react'; import { useParams } from 'react-router-dom'; -export default function SingleTripPage() { +import { BottomNavigationBar, Seo } from '@/widgets'; + +import { ShareTrip } from '@/features/shared'; + +import { LoadingSpinner, QueryErrorBoundary } from '@/shared'; + +export default function ShareTripPage() { const { contentId } = useParams(); return ( <>
}> - +
diff --git a/src/pages/tour/geotrip/index.ts b/src/pages/tour/geotrip/index.ts index 0797c0b2..20640dc7 100644 --- a/src/pages/tour/geotrip/index.ts +++ b/src/pages/tour/geotrip/index.ts @@ -1,2 +1,2 @@ export { default as GeoTrip } from './GeoTrip'; -export { default as SingleTrip } from './SingleTrip'; +export { default as SingleTrip } from './Share'; diff --git a/src/widgets/bottomNavigationBar/utils.ts b/src/widgets/bottomNavigationBar/utils.ts index fd4332e5..2625ba04 100644 --- a/src/widgets/bottomNavigationBar/utils.ts +++ b/src/widgets/bottomNavigationBar/utils.ts @@ -19,9 +19,9 @@ export const createNavItems = ({ navigate }: createNavItemsParams) => { { id: 'home-navigation-tutorial', icon: HomeIcon, - path: '/tour/geo-trip', + path: '/tour/geo/1234566', label: 'home Icon', - onClick: () => navigateTo('/tour/geo-trip'), + onClick: () => navigateTo('/tour/geo/1234566'), }, { id: 'list-navigation-tutorial', From 04d8691973aa68cd1fc8d4fefbdc668ee3418ae5 Mon Sep 17 00:00:00 2001 From: zzzryt Date: Tue, 9 Dec 2025 15:33:24 +0900 Subject: [PATCH 4/7] =?UTF-8?q?(#197):=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx b/src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx index 191669d0..41de329e 100644 --- a/src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx +++ b/src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx @@ -48,7 +48,7 @@ describe('BottomNavigationBar 컴포넌트', () => { await user.click(homeButton); expect(mockNavigate).toHaveBeenCalledWith( - expect.stringContaining('/tour/geo-trip'), + expect.stringContaining('/tour/geo/1234566'), { replace: true }, ); }); From 0df088be09124fffe84ef18d87b5753e585a4124 Mon Sep 17 00:00:00 2001 From: zzzryt Date: Fri, 19 Dec 2025 12:51:30 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor(route):=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router/Router.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/router/Router.tsx b/src/app/router/Router.tsx index 3f6e94f2..dc9bd3f1 100644 --- a/src/app/router/Router.tsx +++ b/src/app/router/Router.tsx @@ -28,7 +28,7 @@ export default function Router() { } /> }> - } /> + } /> } /> } /> } /> From d8abdedae68f80e5007fefff8d1139d04760b20c Mon Sep 17 00:00:00 2001 From: zzzryt Date: Fri, 19 Dec 2025 14:18:54 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor(slide-page):=20=EC=8A=AC=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20ses?= =?UTF-8?q?sionStorage=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auth/ui/AuthButtonContainer.tsx | 2 +- src/features/tourDetail/ui/TourOverview.tsx | 11 ++-- src/features/tourShort/hook/index.ts | 2 +- .../tourShort/hook/usePersistSlideStorage.ts | 43 ++++++++++++++ .../tourShort/hook/usePersistSlideUrl.ts | 50 ----------------- .../tourShort/ui/TourSwiperContainer.tsx | 5 +- src/shared/hooks/index.ts | 1 + src/shared/hooks/useSessionStorage.ts | 56 +++++++++++++++++++ src/widgets/bottomNavigationBar/utils.ts | 4 +- 9 files changed, 111 insertions(+), 63 deletions(-) create mode 100644 src/features/tourShort/hook/usePersistSlideStorage.ts delete mode 100644 src/features/tourShort/hook/usePersistSlideUrl.ts create mode 100644 src/shared/hooks/useSessionStorage.ts 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/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/tourShort/hook/index.ts b/src/features/tourShort/hook/index.ts index 57b031a9..8586ad07 100644 --- a/src/features/tourShort/hook/index.ts +++ b/src/features/tourShort/hook/index.ts @@ -1,2 +1,2 @@ export * from './useInfiniteSwiperControl'; -export * from './usePersistSlideUrl'; +export * from './usePersistSlideStorage'; diff --git a/src/features/tourShort/hook/usePersistSlideStorage.ts b/src/features/tourShort/hook/usePersistSlideStorage.ts new file mode 100644 index 00000000..b0f66866 --- /dev/null +++ b/src/features/tourShort/hook/usePersistSlideStorage.ts @@ -0,0 +1,43 @@ +import { useSessionStorage } from '@/shared'; +import { useCallback } from 'react'; + +interface SlideState { + pageParam: number; + index: number; +} + +export const usePersistSlideStorage = () => { + const [slideState, setSlideState] = useSessionStorage( + 'slideState', + { + index: 0, + pageParam: 1, + }, + ); + + const setSlideParams = useCallback( + ({ index, pageParam }: SlideState) => { + setSlideState({ index: index % 10, pageParam }); + }, + [setSlideState], + ); + + const getSlideIndex = () => { + return slideState.index; + }; + + const getPageParam = () => { + return slideState.pageParam; + }; + + const slideReset = () => { + setSlideState({ index: 0, pageParam: 1 }); + }; + + return { + setSlideParams, + getSlideIndex, + getPageParam, + slideReset, + }; +}; diff --git a/src/features/tourShort/hook/usePersistSlideUrl.ts b/src/features/tourShort/hook/usePersistSlideUrl.ts deleted file mode 100644 index 729c109b..00000000 --- a/src/features/tourShort/hook/usePersistSlideUrl.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useCallback } from 'react'; -import { useSearchParams } from 'react-router-dom'; - -interface handleSlideChangeProps { - pageParam: number; - index: number; -} - -export const usePersistSlideUrl = () => { - const [searchParams, setSearchParams] = useSearchParams(); - - const setSlideParams = useCallback( - ({ index, pageParam }: handleSlideChangeProps) => { - searchParams.set('slide-index', (index % 10).toString()); - searchParams.set('page-param', pageParam.toString()); - setSearchParams(searchParams); - }, - [], - ); - - const getSlideIndex = () => { - const slideIndex = searchParams.get('slide-index'); - if (slideIndex === null) { - return 0; - } - return Number(slideIndex); - }; - - const getPageParam = () => { - const pageParam = searchParams.get('page-param'); - if (pageParam === null) { - return 1; - } - return Number(pageParam); - }; - - const slideReset = () => { - const nextParams = new URLSearchParams(searchParams); - nextParams.delete('slide-index'); - nextParams.delete('page-param'); - setSearchParams(nextParams); - }; - - return { - setSlideParams, - getSlideIndex, - getPageParam, - slideReset, - }; -}; diff --git a/src/features/tourShort/ui/TourSwiperContainer.tsx b/src/features/tourShort/ui/TourSwiperContainer.tsx index e950cc5d..b61b62af 100644 --- a/src/features/tourShort/ui/TourSwiperContainer.tsx +++ b/src/features/tourShort/ui/TourSwiperContainer.tsx @@ -10,7 +10,7 @@ import { TourSwiperView, useTourSwiperInfiniteQuery, useInfiniteSwiperControl, - usePersistSlideUrl, + usePersistSlideStorage, } from '@/features/tourShort'; import { getSuspenseLocation } from '@/shared'; @@ -18,7 +18,8 @@ import type { Swiper as SwiperType } from 'swiper/types'; import type { TourInjected } from '@/features/tour'; function TourSwiperContainer({ distance, contentTypeId }: TourInjected) { - const { setSlideParams, getSlideIndex, getPageParam } = usePersistSlideUrl(); + const { setSlideParams, getSlideIndex, getPageParam } = + usePersistSlideStorage(); const geoLocation = getSuspenseLocation(); const { slideEntries, fetchAppend, fetchPrepend } = diff --git a/src/shared/hooks/index.ts b/src/shared/hooks/index.ts index c3eb0799..156ada1f 100644 --- a/src/shared/hooks/index.ts +++ b/src/shared/hooks/index.ts @@ -1,5 +1,6 @@ export * from './useSyncedState'; export * from './useDebouncedCallback'; export * from './useLocalStorage'; +export * from './useSessionStorage'; export * from './useFunnel'; export * from './useToggleState'; diff --git a/src/shared/hooks/useSessionStorage.ts b/src/shared/hooks/useSessionStorage.ts new file mode 100644 index 00000000..bd777721 --- /dev/null +++ b/src/shared/hooks/useSessionStorage.ts @@ -0,0 +1,56 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +/** + * sessionStorage 값을 저장하고 불러오는 커스텀 훅입니다. + * dispatchEvent를 사용하여 다른 컴포넌트에서 sessionStorage의 변경을 감지할 수 있습니다. + * + * @param {string} key - sessionStorage에서 사용할 키 + * @param {T} initialValue - 초기값 + * @returns {[T, (value: T | ((val: T) => T)) => void]} - 저장된 값과 업데이트 함수를 반환합니다. + * + * @example + * //초기값에 의해, 스토리지에 저장된 정보가 없다면 자동으로 값을 저장합니다. + * const [value, setValue] = useLocalStorage('myKey', 'defaultValue'); + */ +export const useSessionStorage = (key: string, initialValue: T) => { + const readValue = useCallback((): T => { + try { + const item = window.sessionStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + throw new Error(`스토리지를 불러오는데 실패했습니다: ${error}`); + } + }, [key, initialValue]); + + const [storedValue, setStoredValue] = useState(readValue); + const sessionStorageEventRef = useRef(new Event('session-storage')); + + const setValue = (value: T | ((val: T) => T)) => { + try { + const valueToStore = + value instanceof Function ? value(storedValue) : value; + setStoredValue(valueToStore); + window.sessionStorage.setItem(key, JSON.stringify(valueToStore)); + + window.dispatchEvent(sessionStorageEventRef.current); + } catch (error) { + console.error(`Error setting sessionStorage key "${key}":`, error); + } + }; + + useEffect(() => { + const handleStorageChange = () => { + setStoredValue(readValue()); + }; + + window.addEventListener('session-storage', handleStorageChange); + window.addEventListener('storage', handleStorageChange); + + return () => { + window.removeEventListener('session-storage', handleStorageChange); + window.removeEventListener('storage', handleStorageChange); + }; + }, [readValue]); + + return [storedValue, setValue] as const; +}; diff --git a/src/widgets/bottomNavigationBar/utils.ts b/src/widgets/bottomNavigationBar/utils.ts index 2625ba04..b48607a4 100644 --- a/src/widgets/bottomNavigationBar/utils.ts +++ b/src/widgets/bottomNavigationBar/utils.ts @@ -19,9 +19,9 @@ export const createNavItems = ({ navigate }: createNavItemsParams) => { { id: 'home-navigation-tutorial', icon: HomeIcon, - path: '/tour/geo/1234566', + path: '/tour/geo', label: 'home Icon', - onClick: () => navigateTo('/tour/geo/1234566'), + onClick: () => navigateTo('/tour/geo'), }, { id: 'list-navigation-tutorial', From 97123f2533c9a34f54da909636bab7bd29153f28 Mon Sep 17 00:00:00 2001 From: zzzryt Date: Fri, 19 Dec 2025 14:20:57 +0900 Subject: [PATCH 7/7] =?UTF-8?q?(#197):=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx b/src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx index 41de329e..39601d12 100644 --- a/src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx +++ b/src/widgets/bottomNavigationBar/BottomNavigationBar.test.tsx @@ -48,7 +48,7 @@ describe('BottomNavigationBar 컴포넌트', () => { await user.click(homeButton); expect(mockNavigate).toHaveBeenCalledWith( - expect.stringContaining('/tour/geo/1234566'), + expect.stringContaining('/tour/geo'), { replace: true }, ); });