diff --git a/apps/app/app/+native-intent.ts b/apps/app/app/+native-intent.ts index dc7ee350..18dcc41a 100644 --- a/apps/app/app/+native-intent.ts +++ b/apps/app/app/+native-intent.ts @@ -20,7 +20,7 @@ export function redirectSystemPath({ path }: { path: string; initial: boolean }) if (webFromUrl) return `/?web=${encodeURIComponent(webFromUrl)}`; try { - /** share extension → openHostApp(`/?web=${encodeURIComponent('/archive?tab=wish')}`) */ + /** share extension → openHostApp(`/?web=${encodeURIComponent('/archive/wish')}`) */ const url = new URL(path, 'piki://app'); const web = url.searchParams.get('web'); diff --git a/apps/app/components/ShareBottomSheet.tsx b/apps/app/components/ShareBottomSheet.tsx index 6050edcb..f95033f0 100644 --- a/apps/app/components/ShareBottomSheet.tsx +++ b/apps/app/components/ShareBottomSheet.tsx @@ -19,7 +19,7 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { const handleOpenWishlist = () => { /** openHostApp path 규칙: `/{path}?{query}` — `web=...`만 넘기면 `/web=...` 라우트로 해석됨 */ - openHostApp(`/?web=${encodeURIComponent('/archive?tab=wish')}`); + openHostApp(`/?web=${encodeURIComponent('/archive/wish')}`); }; useEffect(() => { diff --git a/apps/app/hooks/useShareIntent.ts b/apps/app/hooks/useShareIntent.ts index be8cbb9e..f563c61d 100644 --- a/apps/app/hooks/useShareIntent.ts +++ b/apps/app/hooks/useShareIntent.ts @@ -5,15 +5,9 @@ import { useCallback, useEffect, useRef } from 'react'; import { toShareIntentPayload } from '@/utils/serializeShareIntent'; import { WebBridge } from '@/utils/webBridge'; -const ARCHIVE_PATH = '/archive'; -const ARCHIVE_WISH_TAB = 'wish'; +const WISHLIST_PATH = '/archive/wish'; -const isArchiveWishTab = (uri: URL) => { - if (uri.pathname !== ARCHIVE_PATH) return false; - - const tab = uri.searchParams.get('tab'); - return tab === null || tab === ARCHIVE_WISH_TAB; -}; +const isWishlistPath = (uri: URL) => uri.pathname === WISHLIST_PATH; type Props = { onChangeWebviewUri: (uri: string) => void; @@ -47,14 +41,14 @@ export const useShareIntent = ({ onChangeWebviewUri, webviewUri }: Props) => { const nextUri = new URL(webviewUri); /** 이미 아카이브 위시 탭: 즉시 전송 */ - if (isArchiveWishTab(nextUri)) { + if (isWishlistPath(nextUri)) { sendShareIntent(); return; } - /** 다른 페이지: payload 보관 후 아카이브 위시 탭으로 이동 → WEB_REQ_READY 수신 시 전송 */ - nextUri.pathname = ARCHIVE_PATH; - nextUri.search = `?tab=${ARCHIVE_WISH_TAB}`; + /** 다른 페이지: payload 보관 후 아카이브 위시로 이동 → WEB_REQ_READY 수신 시 전송 */ + nextUri.pathname = WISHLIST_PATH; + nextUri.search = ''; nextUri.hash = ''; onChangeWebviewUri(nextUri.toString()); } catch { diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index b4f97220..c0f6173c 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -31,6 +31,27 @@ const nextConfig = async () => { ], }, + /** + * 구버전 앱 공유 시트·기존 딥링크의 /archive(?tab=) 경로 호환 + * + * NOTE: 구버전 앱 사라지면 이 설정 제거 필요 + */ + async redirects() { + return [ + { + source: '/archive', + has: [{ type: 'query', key: 'tab', value: 'tournament' }], + destination: '/archive/tournament', + permanent: false, + }, + { + source: '/archive', + destination: '/archive/wish', + permanent: false, + }, + ]; + }, + async rewrites() { return [ { diff --git a/apps/web/src/apis/getWishlist.ts b/apps/web/src/apis/getWishlist.ts index c8802993..3b5c61bf 100644 --- a/apps/web/src/apis/getWishlist.ts +++ b/apps/web/src/apis/getWishlist.ts @@ -4,9 +4,13 @@ import { clientApi } from '@/apis/client'; import { serverApi } from '@/apis/server'; import { ENDPOINTS } from '@/consts/api'; import type { ApiResponseT } from '@/types/api'; +import type { ItemT } from '@/types/item'; +import type { WishItemT, WishT } from '@/types/wish'; -import type { WishlistEntryT } from '../app/archive/_types/wish'; -import type { WishItemT } from '../app/archive/_types/wish'; +type WishlistEntryT = { + wish: WishT; + item: ItemT; +}; type WishlistApiResponseT = ApiResponseT & { pageResponse: { diff --git a/apps/web/src/app/archive/_components/WishlistLayout.tsx b/apps/web/src/app/archive/_common/_components/ArchivePageLayout.tsx similarity index 72% rename from apps/web/src/app/archive/_components/WishlistLayout.tsx rename to apps/web/src/app/archive/_common/_components/ArchivePageLayout.tsx index 66d5e3e7..3e36f736 100644 --- a/apps/web/src/app/archive/_components/WishlistLayout.tsx +++ b/apps/web/src/app/archive/_common/_components/ArchivePageLayout.tsx @@ -1,14 +1,11 @@ -import { Suspense } from 'react'; - import { Header, HeaderIcon } from '@/components/header'; -import WishTab from './WishTab'; - -type WishlistLayoutProps = { +type Props = { + title: string; children: React.ReactNode; }; -function WishlistLayout({ children }: WishlistLayoutProps) { +function ArchivePageLayout({ title, children }: Props) { return (
@@ -22,16 +19,13 @@ function WishlistLayout({ children }: WishlistLayoutProps) { } />

- 내 보관함 + {title}

- - -
{children} ); } -export default WishlistLayout; +export default ArchivePageLayout; diff --git a/apps/web/src/app/archive/_components/WishTab.tsx b/apps/web/src/app/archive/_components/WishTab.tsx deleted file mode 100644 index b3b3311f..00000000 --- a/apps/web/src/app/archive/_components/WishTab.tsx +++ /dev/null @@ -1,57 +0,0 @@ -'use client'; - -import { useRouter, useSearchParams } from 'next/navigation'; - -import { ROUTES } from '@/consts/route'; -import type { ItemTypeT } from '@/types/item'; -import { cn } from '@/utils/cn'; - -import type { WishTabT } from '../_types/wish'; - -const TABS: WishTabT[] = ['저장한 위시템', '토너먼트 기록']; - -const TAB_TYPE: Record = { - '저장한 위시템': 'wish', - '토너먼트 기록': 'tournament', -}; - -const TYPE_TAB: Record = { - wish: '저장한 위시템', - tournament: '토너먼트 기록', -}; - -function WishTab() { - const router = useRouter(); - const searchParams = useSearchParams(); - const tabParam = searchParams.get('tab'); - const activeTab: WishTabT = - tabParam === 'wish' || tabParam === 'tournament' ? TYPE_TAB[tabParam] : '저장한 위시템'; - - const handleTabChange = (tab: WishTabT) => { - router.replace(ROUTES.ARCHIVE(TAB_TYPE[tab])); - }; - - return ( -
-
- {TABS.map(tab => ( - - ))} -
-
- ); -} - -export default WishTab; diff --git a/apps/web/src/app/archive/_components/tournament-history-content/client.tsx b/apps/web/src/app/archive/_components/tournament-history-content/client.tsx deleted file mode 100644 index 10d8ff32..00000000 --- a/apps/web/src/app/archive/_components/tournament-history-content/client.tsx +++ /dev/null @@ -1,18 +0,0 @@ -'use client'; - -import BottomTabBar from '@/components/bottom-tab-bar'; - -import TournamentHistoryList from '../TournamentHistoryList'; - -function TournamentHistoryContentClient() { - return ( - <> - -
- -
- - ); -} - -export default TournamentHistoryContentClient; diff --git a/apps/web/src/app/archive/_types/wish.ts b/apps/web/src/app/archive/_types/wish.ts deleted file mode 100644 index 47673ab0..00000000 --- a/apps/web/src/app/archive/_types/wish.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ItemStatusT, ItemT } from '@/types/item'; -import type { WishT } from '@/types/wish'; - -export type WishTabT = '저장한 위시템' | '토너먼트 기록'; - -export type WishlistEntryT = { - wish: WishT; - item: ItemT; -}; - -export type WishItemT = { - id: number; - itemId: number; - name: string; - price: number; - imageUrl: string | null; - status: ItemStatusT; -}; diff --git a/apps/web/src/app/archive/page.tsx b/apps/web/src/app/archive/page.tsx deleted file mode 100644 index 04734902..00000000 --- a/apps/web/src/app/archive/page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import WishlistLayout from './_components/WishlistLayout'; -import TournamentHistoryContent from './_components/tournament-history-content'; -import WishContent from './_components/wish-content'; -import type { WishTabT } from './_types/wish'; - -type ArchivePageProps = { - searchParams: Promise<{ tab?: string }>; -}; - -const getActiveWishTab = (tab?: string): WishTabT => { - if (tab === 'tournament') return '토너먼트 기록'; - return '저장한 위시템'; -}; - -async function ArchivePage({ searchParams }: ArchivePageProps) { - const { tab } = await searchParams; - const activeTab = getActiveWishTab(tab); - - return ( - - {activeTab === '저장한 위시템' && } - {activeTab === '토너먼트 기록' && } - - ); -} - -export default ArchivePage; diff --git a/apps/web/src/app/archive/tournament/_components/ArchiveTournamentClient.tsx b/apps/web/src/app/archive/tournament/_components/ArchiveTournamentClient.tsx new file mode 100644 index 00000000..9a44d929 --- /dev/null +++ b/apps/web/src/app/archive/tournament/_components/ArchiveTournamentClient.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { Suspense, useState } from 'react'; + +import BottomTabBar from '@/components/bottom-tab-bar'; +import type { TournamentStatusT } from '@/types/tournament'; + +import ArchivePageLayout from '../../_common/_components/ArchivePageLayout'; +import TournamentHistoryList from './TournamentHistoryList'; +import TournamentStatusTab, { type TournamentStatusTabT } from './TournamentStatusTab'; + +const STATUS_BY_TAB: Record = { + 'in-progress': ['PENDING', 'IN_PROGRESS'], + completed: ['COMPLETED'], +}; + +function ArchiveTournamentClient() { + const [activeTab, setActiveTab] = useState('in-progress'); + + return ( + + + +

+ 토너먼트를 불러오는 중이에요 +

+ + } + > + +
+
+ +
+
+ ); +} + +export default ArchiveTournamentClient; diff --git a/apps/web/src/app/archive/_components/tournament-history-content/index.tsx b/apps/web/src/app/archive/tournament/_components/TournamentHistoryContent.tsx similarity index 57% rename from apps/web/src/app/archive/_components/tournament-history-content/index.tsx rename to apps/web/src/app/archive/tournament/_components/TournamentHistoryContent.tsx index 2390d23d..ec562773 100644 --- a/apps/web/src/app/archive/_components/tournament-history-content/index.tsx +++ b/apps/web/src/app/archive/tournament/_components/TournamentHistoryContent.tsx @@ -1,21 +1,24 @@ import { HydrationBoundary, dehydrate } from '@tanstack/react-query'; import { getTournamentList } from '@/apis/getTournamentList'; +import type { TournamentStatusT } from '@/types/tournament'; import { getQueryClient } from '@/utils/queryClient'; -import TournamentHistoryContentClient from './client'; +import ArchiveTournamentClient from './ArchiveTournamentClient'; + +const DEFAULT_STATUSES: TournamentStatusT[] = ['PENDING', 'IN_PROGRESS']; async function TournamentHistoryContent() { const queryClient = getQueryClient(); await queryClient.prefetchQuery({ - queryKey: ['tournamentList', []], - queryFn: () => getTournamentList(), + queryKey: ['tournamentList', DEFAULT_STATUSES], + queryFn: () => getTournamentList(DEFAULT_STATUSES), }); return ( - + ); } diff --git a/apps/web/src/app/archive/_components/TournamentHistoryList.tsx b/apps/web/src/app/archive/tournament/_components/TournamentHistoryList.tsx similarity index 83% rename from apps/web/src/app/archive/_components/TournamentHistoryList.tsx rename to apps/web/src/app/archive/tournament/_components/TournamentHistoryList.tsx index 4d5dc206..d807557f 100644 --- a/apps/web/src/app/archive/_components/TournamentHistoryList.tsx +++ b/apps/web/src/app/archive/tournament/_components/TournamentHistoryList.tsx @@ -3,9 +3,14 @@ import { TrophyIconFill } from '@/assets/icons'; import TournamentCard from '@/components/tournament-card'; import { useGetTournamentList } from '@/hooks/useGetTournamentList'; +import type { TournamentStatusT } from '@/types/tournament'; -function TournamentHistoryList() { - const { tournamentListData } = useGetTournamentList(); +type Props = { + statuses: TournamentStatusT[]; +}; + +function TournamentHistoryList({ statuses }: Props) { + const { tournamentListData } = useGetTournamentList(statuses); if (tournamentListData.length === 0) return ( diff --git a/apps/web/src/app/archive/tournament/_components/TournamentStatusTab.tsx b/apps/web/src/app/archive/tournament/_components/TournamentStatusTab.tsx new file mode 100644 index 00000000..7577c49f --- /dev/null +++ b/apps/web/src/app/archive/tournament/_components/TournamentStatusTab.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { cn } from '@/utils/cn'; + +export type TournamentStatusTabT = 'in-progress' | 'completed'; + +type Props = { + activeTab: TournamentStatusTabT; + onTabChange: (tab: TournamentStatusTabT) => void; +}; + +const TABS: { value: TournamentStatusTabT; label: string }[] = [ + { value: 'in-progress', label: '진행 중' }, + { value: 'completed', label: '완료' }, +]; + +function TournamentStatusTab({ activeTab, onTabChange }: Props) { + return ( +
+ {TABS.map(tab => ( + + ))} +
+ ); +} + +export default TournamentStatusTab; diff --git a/apps/web/src/app/archive/tournament/page.tsx b/apps/web/src/app/archive/tournament/page.tsx new file mode 100644 index 00000000..70d1383b --- /dev/null +++ b/apps/web/src/app/archive/tournament/page.tsx @@ -0,0 +1,7 @@ +import TournamentHistoryContent from './_components/TournamentHistoryContent'; + +async function ArchiveTournamentPage() { + return ; +} + +export default ArchiveTournamentPage; diff --git a/apps/web/src/app/archive/wish/[id]/_hooks/useDeleteWish.ts b/apps/web/src/app/archive/wish/[id]/_hooks/useDeleteWish.ts index 32467efa..5ffb1432 100644 --- a/apps/web/src/app/archive/wish/[id]/_hooks/useDeleteWish.ts +++ b/apps/web/src/app/archive/wish/[id]/_hooks/useDeleteWish.ts @@ -17,7 +17,7 @@ export const useDeleteWish = (wishId: number) => { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['wishlists'] }); toast.success('위시 상품이 삭제되었습니다.'); - router.replace(ROUTES.ARCHIVE()); + router.replace(ROUTES.WISHLIST); }, onError: error => { if (!isAxiosError(error) || !error.response) return; diff --git a/apps/web/src/app/archive/wish/[id]/_hooks/usePatchWish.ts b/apps/web/src/app/archive/wish/[id]/_hooks/usePatchWish.ts index 20ee4d4f..9b869b7d 100644 --- a/apps/web/src/app/archive/wish/[id]/_hooks/usePatchWish.ts +++ b/apps/web/src/app/archive/wish/[id]/_hooks/usePatchWish.ts @@ -41,7 +41,7 @@ export const usePatchWish = (wishId: number) => { toast.error(clientErrorMessage); } else if (status === 403 || status === 404 || status === 409) { toast.error(clientErrorMessage); - router.replace(ROUTES.ARCHIVE()); + router.replace(ROUTES.WISHLIST); } }, }); diff --git a/apps/web/src/app/archive/wish/[id]/layout.tsx b/apps/web/src/app/archive/wish/[id]/layout.tsx index 57363041..c59a703d 100644 --- a/apps/web/src/app/archive/wish/[id]/layout.tsx +++ b/apps/web/src/app/archive/wish/[id]/layout.tsx @@ -28,13 +28,12 @@ async function WishEditLayout({ children, params }: WishEditLayoutProps) { /** 아직 PROCESSING 상태인 경우에는 접근 불가 */ if (wishData.item.status === 'PROCESSING' || wishData.item.status === 'PENDING') - redirect(ROUTES.ARCHIVE()); + redirect(ROUTES.WISHLIST); } catch (error) { if (!isAxiosError(error)) throw error; /** 위시가 존재하지 않는 경우 */ - if (error.response?.status === 404) - redirect(ROUTES.ARCHIVE('wish')); + if (error.response?.status === 404) redirect(ROUTES.WISHLIST); throw error; } diff --git a/apps/web/src/app/archive/_apis/deleteWishes.ts b/apps/web/src/app/archive/wish/_apis/deleteWishes.ts similarity index 100% rename from apps/web/src/app/archive/_apis/deleteWishes.ts rename to apps/web/src/app/archive/wish/_apis/deleteWishes.ts diff --git a/apps/web/src/app/archive/_components/FabMenu.tsx b/apps/web/src/app/archive/wish/_components/FabMenu.tsx similarity index 100% rename from apps/web/src/app/archive/_components/FabMenu.tsx rename to apps/web/src/app/archive/wish/_components/FabMenu.tsx diff --git a/apps/web/src/app/archive/_components/WishAddDialog.tsx b/apps/web/src/app/archive/wish/_components/WishAddDialog.tsx similarity index 100% rename from apps/web/src/app/archive/_components/WishAddDialog.tsx rename to apps/web/src/app/archive/wish/_components/WishAddDialog.tsx diff --git a/apps/web/src/app/archive/_components/WishCardSkeleton.tsx b/apps/web/src/app/archive/wish/_components/WishCardSkeleton.tsx similarity index 100% rename from apps/web/src/app/archive/_components/WishCardSkeleton.tsx rename to apps/web/src/app/archive/wish/_components/WishCardSkeleton.tsx diff --git a/apps/web/src/app/archive/_components/wish-content/index.tsx b/apps/web/src/app/archive/wish/_components/WishContent.tsx similarity index 93% rename from apps/web/src/app/archive/_components/wish-content/index.tsx rename to apps/web/src/app/archive/wish/_components/WishContent.tsx index 983054e1..7f7e5980 100644 --- a/apps/web/src/app/archive/_components/wish-content/index.tsx +++ b/apps/web/src/app/archive/wish/_components/WishContent.tsx @@ -4,7 +4,7 @@ import type { WishlistPageT } from '@/apis/getWishlist'; import { getWishlist } from '@/apis/getWishlist'; import { getQueryClient } from '@/utils/queryClient'; -import WishContentClient from './client'; +import WishContentClient from './WishContentClient'; async function WishContent() { const queryClient = getQueryClient(); diff --git a/apps/web/src/app/archive/_components/wish-content/client.tsx b/apps/web/src/app/archive/wish/_components/WishContentClient.tsx similarity index 75% rename from apps/web/src/app/archive/_components/wish-content/client.tsx rename to apps/web/src/app/archive/wish/_components/WishContentClient.tsx index 522dc0d3..c48fc42c 100644 --- a/apps/web/src/app/archive/_components/wish-content/client.tsx +++ b/apps/web/src/app/archive/wish/_components/WishContentClient.tsx @@ -2,12 +2,12 @@ import { useState } from 'react'; -import { useWishlistDelete } from '../../_hooks/useDeleteWishes'; -import { useShareIntentWish } from '../../_hooks/useShareIntentWish'; -import WishAddDialog from '../WishAddDialog'; -import WishlistBottomBar from '../WishlistBottomBar'; -import WishlistFabArea from '../WishlistFabArea'; -import WishlistList from '../WishlistList'; +import { useDeleteWishes } from '../_hooks/useDeleteWishes'; +import { useShareIntentWish } from '../_hooks/useShareIntentWish'; +import WishAddDialog from './WishAddDialog'; +import WishlistBottomBar from './WishlistBottomBar'; +import WishlistFabArea from './WishlistFabArea'; +import WishlistList from './WishlistList'; function WishContentClient() { const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); @@ -20,7 +20,7 @@ function WishContentClient() { handleEnterDeleteMode, handleToggleSelect, handleConfirmDelete, - } = useWishlistDelete(); + } = useDeleteWishes(); return ( <> diff --git a/apps/web/src/app/archive/_components/WishFab.tsx b/apps/web/src/app/archive/wish/_components/WishFab.tsx similarity index 100% rename from apps/web/src/app/archive/_components/WishFab.tsx rename to apps/web/src/app/archive/wish/_components/WishFab.tsx diff --git a/apps/web/src/app/archive/_components/WishlistTabContent.tsx b/apps/web/src/app/archive/wish/_components/WishGridContent.tsx similarity index 75% rename from apps/web/src/app/archive/_components/WishlistTabContent.tsx rename to apps/web/src/app/archive/wish/_components/WishGridContent.tsx index 490ca5f2..1a3b22de 100644 --- a/apps/web/src/app/archive/_components/WishlistTabContent.tsx +++ b/apps/web/src/app/archive/wish/_components/WishGridContent.tsx @@ -1,21 +1,21 @@ -import WishGrid from '@/app/archive/_components/wish-grid'; import { HeartIconFill } from '@/assets/icons'; +import type { WishItemT } from '@/types/wish'; -import type { WishItemT } from '../_types/wish'; +import WishGrid from './wish-grid'; -type WishlistTabContentProps = { +type WishGridContentProps = { items: WishItemT[]; isDeleteMode?: boolean; selectedIds?: Set; onToggleSelect?: (id: number) => void; }; -function WishlistTabContent({ +function WishGridContent({ items, isDeleteMode, selectedIds, onToggleSelect, -}: WishlistTabContentProps) { +}: WishGridContentProps) { if (items.length === 0) { return (
@@ -35,4 +35,4 @@ function WishlistTabContent({ ); } -export default WishlistTabContent; +export default WishGridContent; diff --git a/apps/web/src/app/archive/_components/WishlistBottomBar.tsx b/apps/web/src/app/archive/wish/_components/WishlistBottomBar.tsx similarity index 100% rename from apps/web/src/app/archive/_components/WishlistBottomBar.tsx rename to apps/web/src/app/archive/wish/_components/WishlistBottomBar.tsx diff --git a/apps/web/src/app/archive/_components/WishlistFabArea.tsx b/apps/web/src/app/archive/wish/_components/WishlistFabArea.tsx similarity index 100% rename from apps/web/src/app/archive/_components/WishlistFabArea.tsx rename to apps/web/src/app/archive/wish/_components/WishlistFabArea.tsx diff --git a/apps/web/src/app/archive/_components/WishlistList.tsx b/apps/web/src/app/archive/wish/_components/WishlistList.tsx similarity index 95% rename from apps/web/src/app/archive/_components/WishlistList.tsx rename to apps/web/src/app/archive/wish/_components/WishlistList.tsx index 8b383658..e9e7e161 100644 --- a/apps/web/src/app/archive/_components/WishlistList.tsx +++ b/apps/web/src/app/archive/wish/_components/WishlistList.tsx @@ -7,7 +7,7 @@ import { useSSEFallback } from '@/hooks/useSSEFallback'; import { hasParsingItems } from '@/utils/item'; import WishCardSkeleton from './WishCardSkeleton'; -import WishlistTabContent from './WishlistTabContent'; +import WishGridContent from './WishGridContent'; type WishlistListProps = { isDeleteMode: boolean; @@ -37,7 +37,7 @@ function WishlistList({ isDeleteMode, selectedIds, onToggleSelect }: WishlistLis return ( <> - { +export const useDeleteWishes = () => { const [isDeleteMode, setIsDeleteMode] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); diff --git a/apps/web/src/app/archive/_hooks/useShareIntentWish.ts b/apps/web/src/app/archive/wish/_hooks/useShareIntentWish.ts similarity index 81% rename from apps/web/src/app/archive/_hooks/useShareIntentWish.ts rename to apps/web/src/app/archive/wish/_hooks/useShareIntentWish.ts index 1ab59930..80368a03 100644 --- a/apps/web/src/app/archive/_hooks/useShareIntentWish.ts +++ b/apps/web/src/app/archive/wish/_hooks/useShareIntentWish.ts @@ -29,22 +29,26 @@ const getUrlFromShareIntent = (payload: ShareIntentPayloadT): string | null => { /** 앱 공유(ShareIntent)로 전달된 상품 URL을 위시리스트에 등록 */ export const useShareIntentWish = () => { - const hasProcessedRef = useRef(false); + /** 같은 URL의 중복 전달만 방지 — 후속 공유는 계속 처리돼야 한다 */ + const processedUrlsRef = useRef(new Set()); const { postWishLinkMutation } = usePostWishLink(); const handleShareIntent = useCallback( (payload: ShareIntentPayloadT) => { - if (hasProcessedRef.current) return; - const url = getUrlFromShareIntent(payload); if (!url) { toast.error('공유된 링크를 찾을 수 없어요'); return; } - hasProcessedRef.current = true; - postWishLinkMutation(url); + if (processedUrlsRef.current.has(url)) return; + + processedUrlsRef.current.add(url); + /** 실패한 URL은 잠금 해제해 재공유 시 다시 시도되도록 */ + postWishLinkMutation(url, { + onError: () => processedUrlsRef.current.delete(url), + }); }, [postWishLinkMutation] ); diff --git a/apps/web/src/app/archive/wish/page.tsx b/apps/web/src/app/archive/wish/page.tsx new file mode 100644 index 00000000..0c81bbb9 --- /dev/null +++ b/apps/web/src/app/archive/wish/page.tsx @@ -0,0 +1,12 @@ +import ArchivePageLayout from '../_common/_components/ArchivePageLayout'; +import WishContent from './_components/WishContent'; + +async function ArchiveWishPage() { + return ( + + + + ); +} + +export default ArchiveWishPage; diff --git a/apps/web/src/app/notification/_utils/getNotificationRoute.ts b/apps/web/src/app/notification/_utils/getNotificationRoute.ts index 41d5da4f..efe9b5b7 100644 --- a/apps/web/src/app/notification/_utils/getNotificationRoute.ts +++ b/apps/web/src/app/notification/_utils/getNotificationRoute.ts @@ -22,7 +22,7 @@ export const getNotificationRoute = ( if (extra?.kind === 'TOURNAMENT' && extra.tournamentId) { return ROUTES.TOURNAMENT_CREATE(extra.tournamentId); } - return ROUTES.ARCHIVE(); + return ROUTES.WISHLIST; case 'ANNOUNCEMENT': return null; } diff --git a/apps/web/src/app/tournament/[id]/create/by-wish/_components/WishSelectCard.tsx b/apps/web/src/app/tournament/[id]/create/by-wish/_components/WishSelectCard.tsx index 681d1d52..b1ffcf5c 100644 --- a/apps/web/src/app/tournament/[id]/create/by-wish/_components/WishSelectCard.tsx +++ b/apps/web/src/app/tournament/[id]/create/by-wish/_components/WishSelectCard.tsx @@ -1,6 +1,6 @@ -import WishCard from '@/app/archive/_components/wish-grid/WishCard'; import CheckboxEmptyIconFill from '@/assets/icons/fill/checkbox-empty.svg'; import CheckboxSelectedIconFill from '@/assets/icons/fill/checkbox-selected.svg'; +import WishCard from '@/components/common/wish-card'; type WishSelectCardProps = { name: string; diff --git a/apps/web/src/components/bottom-tab-bar/index.tsx b/apps/web/src/components/bottom-tab-bar/index.tsx index ff0a2603..ada6ca03 100644 --- a/apps/web/src/components/bottom-tab-bar/index.tsx +++ b/apps/web/src/components/bottom-tab-bar/index.tsx @@ -10,9 +10,12 @@ import { ROUTES } from '@/consts/route'; const TABS = [ { label: '홈', icon: HomeIconFill, href: ROUTES.HOME }, - { label: '보관', icon: HeartIconFill, href: ROUTES.ARCHIVE() }, + { label: '보관', icon: HeartIconFill, href: ROUTES.WISHLIST }, ] as const; +const matchesTabPath = (pathname: string, basePath: string) => + pathname === basePath || pathname.startsWith(`${basePath}/`); + function BottomTabBar() { const router = useRouter(); const pathname = usePathname(); @@ -43,8 +46,9 @@ function BottomTabBar() { {TABS.map(({ label, icon: Icon, href }) => { const isActive = label === '보관' - ? pathname.startsWith(ROUTES.ARCHIVE_BASE) - : pathname === href || pathname.startsWith(`${href}/`); + ? matchesTabPath(pathname, ROUTES.WISHLIST) || + matchesTabPath(pathname, ROUTES.TOURNAMENT_HISTORY) + : matchesTabPath(pathname, href); return (