diff --git a/apps/web/e2e/specs/tournament/tournamentItemAdd.spec.ts b/apps/web/e2e/specs/tournament/tournamentItemAdd.spec.ts index 2ada2a41..4d4593a2 100644 --- a/apps/web/e2e/specs/tournament/tournamentItemAdd.spec.ts +++ b/apps/web/e2e/specs/tournament/tournamentItemAdd.spec.ts @@ -64,7 +64,7 @@ test('위시에서 상품 4개를 가져오면 장바구니에 담긴다', async await expect(nextButton).toBeDisabled(); for (const { item } of MOCK_WISHLIST_ENTRIES) { - await page.getByRole('button', { name: item.name }).click(); + await page.getByRole('button', { name: item.name ?? '' }).click(); } /** 담기 성공 후 재조회부터는 4개 담긴 응답 (뒤에 등록한 목이 우선 매칭) */ diff --git a/apps/web/src/app/archive/wish/[id]/_types/wish.ts b/apps/web/src/app/archive/wish/[id]/_types/wish.ts index 5bb37c68..b551c192 100644 --- a/apps/web/src/app/archive/wish/[id]/_types/wish.ts +++ b/apps/web/src/app/archive/wish/[id]/_types/wish.ts @@ -19,6 +19,13 @@ type ItemT = { price: null; currency: null; } + | { + status: (typeof ITEM_STATUS)['INCOMPLETE']; + name: string | null; + imageUrl: string | null; + price: number | null; + currency: string | null; + } | { status: (typeof ITEM_STATUS)['READY']; name: string; diff --git a/apps/web/src/app/archive/wish/_components/wish-grid/WishFailedCard.tsx b/apps/web/src/app/archive/wish/_components/wish-grid/WishFailedCard.tsx index 053504c2..abdf32ea 100644 --- a/apps/web/src/app/archive/wish/_components/wish-grid/WishFailedCard.tsx +++ b/apps/web/src/app/archive/wish/_components/wish-grid/WishFailedCard.tsx @@ -1,13 +1,13 @@ import { WarningIconFill } from '@/assets/icons'; -function WishFailedCard() { +function WishFailedCard({ message }: { message: string }) { return (
-

가져오는데 실패했어요

+

{message}

직접 입력하기

diff --git a/apps/web/src/app/archive/wish/_components/wish-grid/index.tsx b/apps/web/src/app/archive/wish/_components/wish-grid/index.tsx index 19fdc43b..a71c3271 100644 --- a/apps/web/src/app/archive/wish/_components/wish-grid/index.tsx +++ b/apps/web/src/app/archive/wish/_components/wish-grid/index.tsx @@ -3,6 +3,7 @@ import type { MouseEvent } from 'react'; import { CheckboxEmptyIconFill, CheckboxSelectedIconFill } from '@/assets/icons'; import WishCard from '@/components/common/wish-card'; +import { ITEM_STATUS } from '@/consts/item'; import { ROUTES } from '@/consts/route'; import { Z_INDEX } from '@/consts/zIndex'; import type { GetWishlistResponseT } from '@/types/wish'; @@ -29,7 +30,7 @@ function WishGrid({ items, isDeleteMode = false, selectedIds, onToggleSelect }: return (
{items.map(({ wish, item }, index) => { - if (item.status === 'FAILED') + if (item.status === ITEM_STATUS.FAILED || item.status === ITEM_STATUS.INCOMPLETE) return ( handleCardClick(event, wish.id)} > - + ); - else if (item.status === 'PENDING' || item.status === 'PROCESSING') + else if (item.status === ITEM_STATUS.PENDING || item.status === ITEM_STATUS.PROCESSING) return ; if (isDeleteMode) { diff --git a/apps/web/src/app/tournament/[id]/create/_components/TournamentCreateClient.tsx b/apps/web/src/app/tournament/[id]/create/_components/TournamentCreateClient.tsx index 3d7a4918..4e283562 100644 --- a/apps/web/src/app/tournament/[id]/create/_components/TournamentCreateClient.tsx +++ b/apps/web/src/app/tournament/[id]/create/_components/TournamentCreateClient.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'; import { Dialog } from '@/components/dialog'; import GetItemDialogContent from '@/components/get-item-dialog'; +import { ITEM_STATUS } from '@/consts/item'; import { QUERY_ACTION } from '@/consts/queryAction'; import { useGetMe } from '@/hooks/useGetMe'; import { useQueryAction } from '@/hooks/useQueryAction'; @@ -75,6 +76,10 @@ function TournamentCreateClient({ tournamentId }: TournamentCreateClientProps) { const hasPendingItem = hasParsingItems(pending?.items ?? []); useSSEFallback(['tournament', tournamentId], hasPendingItem); + const hasUnfinishedItem = (pending?.items ?? []).some( + item => item.status === ITEM_STATUS.FAILED || item.status === ITEM_STATUS.INCOMPLETE + ); + // 주최자가 ROOT 를 시작한 후(ownerStarted=true) 에는 초대 기간이 이미 종료된 상태라 // inviteExpiresAt 이 null 이고 담기 마감 카운트다운도 의미 없다. const ownerStarted = pending?.ownerStarted ?? false; @@ -211,9 +216,7 @@ function TournamentCreateClient({ tournamentId }: TournamentCreateClientProps) { item.status === 'FAILED') ?? false) - } + hasUnreadyItem={hasPendingItem || hasUnfinishedItem} hasFriends={hasFriends} isWaitingForOwnerStart={isWaitingForOwnerStart} isDepositClosed={isDepositClosed} diff --git a/apps/web/src/app/tournament/[id]/create/_components/product-image/index.tsx b/apps/web/src/app/tournament/[id]/create/_components/product-image/index.tsx index d7be46cb..5b826eff 100644 --- a/apps/web/src/app/tournament/[id]/create/_components/product-image/index.tsx +++ b/apps/web/src/app/tournament/[id]/create/_components/product-image/index.tsx @@ -4,6 +4,7 @@ import type { ImageProps } from 'next/image'; import type { ReactNode } from 'react'; import BaseImage from '@/components/base-image'; +import { ITEM_STATUS } from '@/consts/item'; import type { ItemStatusT } from '@/types/item'; import { cn } from '@/utils/cn'; @@ -51,8 +52,11 @@ function ProductImage({ className={containerClass} {...(!fill ? { style: { width: dimension, height: dimension } } : {})} > - {(parsingStatus === 'PENDING' || parsingStatus === 'PROCESSING') && } - {parsingStatus === 'FAILED' && + {(parsingStatus === ITEM_STATUS.PENDING || parsingStatus === ITEM_STATUS.PROCESSING) && ( + + )} + {/* INCOMPLETE 는 이미지를 못 건진 경우라 FAILED 와 같은 빈 이미지 자리를 쓴다 (디자인 확인 전 임시) */} + {(parsingStatus === ITEM_STATUS.FAILED || parsingStatus === ITEM_STATUS.INCOMPLETE) && (size === 'lg' ? : )}
); diff --git a/apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentBasketItem.tsx b/apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentBasketItem.tsx index 02228380..1a96f92b 100644 --- a/apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentBasketItem.tsx +++ b/apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentBasketItem.tsx @@ -1,6 +1,7 @@ import Image from 'next/image'; import ProductImage from '@/app/tournament/[id]/create/_components/product-image'; +import { ITEM_STATUS } from '@/consts/item'; import { Z_INDEX } from '@/consts/zIndex'; import { useGetMe } from '@/hooks/useGetMe'; import { cn } from '@/utils/cn'; @@ -28,7 +29,10 @@ function TournamentBasketItem({
diff --git a/apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasket.tsx b/apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasket.tsx index 730f7834..9b252391 100644 --- a/apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasket.tsx +++ b/apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasket.tsx @@ -6,6 +6,7 @@ import { useState } from 'react'; import AddIcon from '@/assets/icons/fill/add.svg'; import { Dialog, DialogTrigger } from '@/components/dialog'; import GetItemDialogContent from '@/components/get-item-dialog'; +import { ITEM_STATUS } from '@/consts/item'; import { ROUTES } from '@/consts/route'; import type { TournamentPendingItemT } from '../../../_common/_types/tournamentResponse'; @@ -76,7 +77,7 @@ function TournamentItemBasket({
{addSlot} {items.map((item, index) => { - if (item.status === 'READY') { + if (item.status === ITEM_STATUS.READY || item.status === ITEM_STATUS.INCOMPLETE) { return ( i.itemId) ?? []); const items = wishlistData.filter( ({ item }) => - item.status !== 'FAILED' && item.status !== 'PROCESSING' && !existingItemIds.has(item.id) + item.status !== ITEM_STATUS.FAILED && + item.status !== ITEM_STATUS.PROCESSING && + item.status !== ITEM_STATUS.INCOMPLETE && + !existingItemIds.has(item.id) ); const isWishlistLoaded = !hasNextPage && !isFetchingNextPage; 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 fc804330..03154cde 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 @@ -2,8 +2,8 @@ import { CheckboxEmptyIconFill, CheckboxSelectedIconFill } from '@/assets/icons' import WishCard from '@/components/common/wish-card'; type WishSelectCardProps = { - name: string; - price: number; + name: string | null; + price: number | null; imageUrl: string | null; sourcePlatform?: string | null; isSelected: boolean; diff --git a/apps/web/src/app/tournament/[id]/item/[itemId]/_types/tournamentItem.ts b/apps/web/src/app/tournament/[id]/item/[itemId]/_types/tournamentItem.ts index 2d9f7983..2c665b80 100644 --- a/apps/web/src/app/tournament/[id]/item/[itemId]/_types/tournamentItem.ts +++ b/apps/web/src/app/tournament/[id]/item/[itemId]/_types/tournamentItem.ts @@ -18,6 +18,12 @@ export type GetTournamentItemResponseT = { imageUrl?: undefined; price?: undefined; } + | { + status: (typeof ITEM_STATUS)['INCOMPLETE']; + name?: string; + imageUrl?: string; + price?: number; + } | { status: (typeof ITEM_STATUS)['READY']; name: string; diff --git a/apps/web/src/components/common/wish-card/index.tsx b/apps/web/src/components/common/wish-card/index.tsx index 64b95843..6858d584 100644 --- a/apps/web/src/components/common/wish-card/index.tsx +++ b/apps/web/src/components/common/wish-card/index.tsx @@ -4,8 +4,8 @@ import Skeleton from '@/components/skeleton'; import formatPrice from '@/utils/formatPrice'; type WishCardProps = { - name: string; - price: number; + name: string | null; + price: number | null; imageUrl: string | null; sourcePlatform?: string | null; preload?: boolean; @@ -19,7 +19,7 @@ function WishCard({ name, price, imageUrl, sourcePlatform, preload = false }: Wi {imageUrl ? ( } @@ -40,7 +40,9 @@ function WishCard({ name, price, imageUrl, sourcePlatform, preload = false }: Wi {/* 상품명 + 가격 + 커머스칩 */}
-

{name}

+

+ {name} +

{formatPrice(String(price))}

{sourcePlatform && ( diff --git a/apps/web/src/consts/item.ts b/apps/web/src/consts/item.ts index 83c12e4a..8d8d44ac 100644 --- a/apps/web/src/consts/item.ts +++ b/apps/web/src/consts/item.ts @@ -2,5 +2,6 @@ export const ITEM_STATUS = { PENDING: 'PENDING', PROCESSING: 'PROCESSING', READY: 'READY', + INCOMPLETE: 'INCOMPLETE', FAILED: 'FAILED', } as const; diff --git a/apps/web/src/types/item.ts b/apps/web/src/types/item.ts index d5ffe477..5a4944be 100644 --- a/apps/web/src/types/item.ts +++ b/apps/web/src/types/item.ts @@ -5,8 +5,8 @@ export type ItemTypeT = 'wish' | 'tournament'; export type ItemT = { id: number; status: ItemStatusT; - name: string; - price: number; + name: string | null; + price: number | null; currency: string | null; imageUrl: string | null; sourceUrl: string | null;