Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/e2e/specs/tournament/tournamentItemAdd.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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개 담긴 응답 (뒤에 등록한 목이 우선 매칭) */
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/app/archive/wish/[id]/_types/wish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { WarningIconFill } from '@/assets/icons';

function WishFailedCard() {
function WishFailedCard({ message }: { message: string }) {
return (
<div className="relative flex flex-col bg-black/5">
<div className="aspect-[201/166] w-full" />
<div className="h-[124px]" />
<div className="absolute inset-0 flex flex-col items-center justify-center gap-3 p-4">
<WarningIconFill className="size-6 text-icon-neutral-secondary" />
<p className="body-2-semibold text-text-neutral-secondary">가져오는데 실패했어요</p>
<p className="body-2-semibold text-text-neutral-secondary">{message}</p>
<p className="body-2-medium text-text-neutral-secondary underline underline-offset-[3px]">
직접 입력하기
</p>
Expand Down
13 changes: 10 additions & 3 deletions apps/web/src/app/archive/wish/_components/wish-grid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,18 +30,24 @@ function WishGrid({ items, isDeleteMode = false, selectedIds, onToggleSelect }:
return (
<div className="grid grid-cols-2">
{items.map(({ wish, item }, index) => {
if (item.status === 'FAILED')
if (item.status === ITEM_STATUS.FAILED || item.status === ITEM_STATUS.INCOMPLETE)
return (
<Link
href={ROUTES.WISH_EDIT(wish.id)}
key={wish.id}
data-scroll-anchor-id={wish.id}
onClick={event => handleCardClick(event, wish.id)}
>
<WishFailedCard />
<WishFailedCard
message={
item.status === ITEM_STATUS.INCOMPLETE
? '일부만 가져왔어요'
: '가져오는데 실패했어요'
}
/>
</Link>
);
else if (item.status === 'PENDING' || item.status === 'PROCESSING')
else if (item.status === ITEM_STATUS.PENDING || item.status === ITEM_STATUS.PROCESSING)
return <WishProcessingCard key={wish.id} />;

if (isDeleteMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -211,9 +216,7 @@ function TournamentCreateClient({ tournamentId }: TournamentCreateClientProps) {
<TournamentStartButton
count={pending?.items.length ?? 0}
tournamentId={tournamentId}
hasUnreadyItem={
hasPendingItem || (pending?.items.some(item => item.status === 'FAILED') ?? false)
}
hasUnreadyItem={hasPendingItem || hasUnfinishedItem}
hasFriends={hasFriends}
isWaitingForOwnerStart={isWaitingForOwnerStart}
isDepositClosed={isDepositClosed}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -51,8 +52,11 @@ function ProductImage({
className={containerClass}
{...(!fill ? { style: { width: dimension, height: dimension } } : {})}
>
{(parsingStatus === 'PENDING' || parsingStatus === 'PROCESSING') && <LoadingFallback />}
{parsingStatus === 'FAILED' &&
{(parsingStatus === ITEM_STATUS.PENDING || parsingStatus === ITEM_STATUS.PROCESSING) && (
<LoadingFallback />
)}
{/* INCOMPLETE 는 이미지를 못 건진 경우라 FAILED 와 같은 빈 이미지 자리를 쓴다 (디자인 확인 전 임시) */}
{(parsingStatus === ITEM_STATUS.FAILED || parsingStatus === ITEM_STATUS.INCOMPLETE) &&
(size === 'lg' ? <LgErrorFallback radius={radius} /> : <SmErrorFallback />)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -28,7 +29,10 @@ function TournamentBasketItem({
<div
className={cn(
'relative aspect-square w-full',
(item.status === 'READY' || item.status === 'FAILED') && 'cursor-pointer'
(item.status === ITEM_STATUS.READY ||
item.status === ITEM_STATUS.FAILED ||
item.status === ITEM_STATUS.INCOMPLETE) &&
'cursor-pointer'
)}
onClick={onClick}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -76,7 +77,7 @@ function TournamentItemBasket({
<div className="grid w-[45%] grid-cols-2 gap-x-6 gap-y-5 pt-[20%]">
{addSlot}
{items.map((item, index) => {
if (item.status === 'READY') {
if (item.status === ITEM_STATUS.READY || item.status === ITEM_STATUS.INCOMPLETE) {
return (
<Link
key={item.tournamentItemId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import BottomCta from '@/components/bottom-cta';
import Button from '@/components/button';
import { Header, HeaderIcon } from '@/components/header';
import TournamentErrorDialog from '@/components/tournament-error-dialog';
import { ITEM_STATUS } from '@/consts/item';
import { ROUTES } from '@/consts/route';
import { useBackWithFallback } from '@/hooks/useBackWithFallback';
import { useGetWishlist } from '@/hooks/useGetWishlist';
Expand Down Expand Up @@ -37,7 +38,10 @@ function ByWishContent({ tournamentId }: ByWishContentProps) {
const existingItemIds = new Set(pending?.items.map(i => 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 6 additions & 4 deletions apps/web/src/components/common/wish-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,7 +19,7 @@ function WishCard({ name, price, imageUrl, sourcePlatform, preload = false }: Wi
{imageUrl ? (
<BaseImage
src={imageUrl}
alt={name}
alt={name ?? ''}
sizes="(max-width: 480px) calc(100vw - 40px - 8px), 216px"
preload={preload}
loadingFallback={<Skeleton className="absolute inset-0 rounded-none" />}
Expand All @@ -40,7 +40,9 @@ function WishCard({ name, price, imageUrl, sourcePlatform, preload = false }: Wi
{/* 상품명 + 가격 + 커머스칩 */}
<div className="flex h-[124px] flex-col items-start gap-2.5 self-stretch p-4">
<div className="flex flex-col gap-1 self-stretch">
<p className="line-clamp-2 self-stretch body-2-medium text-text-neutral-primary">{name}</p>
<p className="line-clamp-2 self-stretch body-2-medium text-text-neutral-primary">
{name}
</p>
<p className="body-2-semibold text-text-neutral-primary">{formatPrice(String(price))}</p>
</div>
{sourcePlatform && (
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/consts/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export const ITEM_STATUS = {
PENDING: 'PENDING',
PROCESSING: 'PROCESSING',
READY: 'READY',
INCOMPLETE: 'INCOMPLETE',
FAILED: 'FAILED',
} as const;
4 changes: 2 additions & 2 deletions apps/web/src/types/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading