From a1f814fe4866fda6f81fd8efbe87153b577c3f3a Mon Sep 17 00:00:00 2001 From: zzzryt Date: Tue, 9 Dec 2025 15:29:42 +0900 Subject: [PATCH 1/5] =?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 2/5] =?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 3/5] =?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 4/5] =?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 5/5] =?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 }, ); });