Skip to content

feat: 소셜 토너먼트 API 연동 및 미구현 UI 보완#159

Closed
ychany wants to merge 45 commits into
devfrom
feat/157-social-tournament-api-integration
Closed

feat: 소셜 토너먼트 API 연동 및 미구현 UI 보완#159
ychany wants to merge 45 commits into
devfrom
feat/157-social-tournament-api-integration

Conversation

@ychany

@ychany ychany commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

작업 요약

  • 닉네임 중복 체크 API 연동
  • 초대 코드/링크 미리보기 API 연동 (by-invite-code + invite-preview)
  • 비회원 게스트 토너먼트 참여 API 연동 (join/guest)
  • 플레이 링크 생성 API 연동 + 공유 URL 동적 구성
  • 친구 초대 다이얼로그 동적 데이터 연동 (만료시간/링크)
  • 주최자 분기를 tournamentData.isOwner 로 통일
  • 담기 마감을 초대 코드 만료 시간으로 통일

작업 세부 내용

API 연동

  • 닉네임 중복 체크GET /users/nickname/check 연동
    • JoinPreviewClient 닉네임 입력에 debounce(300ms) + 인라인 헬퍼/CTA 비활성
    • 최대 10자 제한 (서버 spec 일치)
  • 초대 코드 검증GET /tournaments/by-invite-code 연동 (백엔드 신규 endpoint)
    • InviteCodeDialog 의 mock 정답 코드(111111) 제거
    • 코드 형식 [A-Z]{3}\d{3} 정규식 검증 (자동 대문자 변환, placeholder ex. ABC123)
    • 400/409 에러 시 InvalidCodeDialog 노출
  • 토너먼트 미리보기GET /tournaments/{id}/invite-preview 연동
    • JoinPreviewClientMOCK_TOURNAMENT_PREVIEW 제거
    • RSC prefetch + HydrationBoundary 패턴
  • 비회원 게스트 참여POST /tournaments/{id}/join/guest 연동
    • JoinPreviewClientgetTournamentList 우회 라우팅 로직 제거
    • 응답 tournamentId 로 라우팅
  • 플레이 링크 생성POST /tournaments/{id}/play-link 연동
    • PlateShareDialog 의 더미 URL 제거
    • 클릭 시 mutation → 응답 playLinkExpiresAt 으로 만료 라벨 갱신 → Web Share API
    • 공유 URL: ${origin}/play/{tournamentId}

응답 타입 확장

  • tournamentData.isOwner (전 상태 공통) — 서버 boolean 필드로 주최자 분기
  • tournamentData.pending.inviteCode, inviteExpiresAt — 친구 초대 동적 데이터
  • tournamentData.completed.playLinkExpiresAt?, hasGroupResult — 결과 공유/그룹 결과 분기

컴포넌트 정리

  • TournamentCreateClientMOCK_DEPOSIT_DURATION_MS 제거 → inviteExpiresAt 기반 deadline
  • InviteFriendsDialogtournamentData.pending.inviteExpiresAt 으로 만료 시간 동적 표시, 최대 7명 → 8명 정책 일치
  • ParticipantPanelinviteCode 기반 초대 URL 동적 구성 (${origin}/invite/{tournamentId}?code=XXX)
  • PlateShareDialog — 서버 응답값 우선 표시, 14일 추정은 fallback
  • TournamentCreateClientlocalStorage 기반 isParticipantOf 제거 → !tournamentData.isOwner

mock / dead code 제거

  • mocks/deposit.ts 삭제
  • mocks/tournamentPreview.ts 삭제
  • joinSession.tsmarkAsParticipant / isParticipantOf / safeParseIds / PARTICIPANT_KEY 삭제
  • verifyInviteCode 함수 → 형식 검증 함수(isValidInviteCodeFormat) 로 축소
  • 풀스크린 dead route /tournament/join + InviteClient.tsx 삭제
  • ROUTES.TOURNAMENT_JOIN_BY_CODE 상수 제거

잔여 mock

  • verifyInviteCode.ts 의 mock 정답 코드는 제거됐고, 형식 검증 함수만 남음
  • MOCK_PARTICIPANTS + withMockParticipants 는 친구 분기 UI 검증용으로 유지 (서버 participants 응답 형태가 chip 그리드의 itemCount 와 분담이 달라 추후 별도 정리)
  • clientApi 의 baseURL/refresh body 빈 객체 처리 보완

후속 작업

다음 항목은 시안/별도 라우트가 필요해 후속 이슈로 분리:

  • 플레이 링크 진입 흐름 (play-link-info + from-play-link, /play/[id] 라우트)
  • 그룹 결과 보기 (group-result, 결과 화면 진입 버튼 + 비교 UI)

연관 이슈

closes #157

Summary by CodeRabbit

Release Notes

  • New Features

    • 초대 코드를 입력하여 토너먼트에 입장할 수 있습니다.
    • 토너먼트 결과 페이지에서 플레이 링크를 공유할 수 있습니다.
    • 닉네임 중복 여부를 실시간으로 확인합니다.
    • 입금 마감 시간을 카운트다운으로 표시합니다.
    • 토너먼트 참여 시 프로필 선택 및 확인 프로세스를 추가했습니다.
  • Documentation

    • 소셜 토너먼트 API 연동 가이드 문서를 추가했습니다.

ychany added 30 commits June 3, 2026 02:59
@vercel

vercel Bot commented Jun 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
depromeet Ready Ready Preview, Comment Jun 6, 2026 4:58pm

@github-actions
github-actions Bot requested review from iOdiO89 and soyeong0115 June 6, 2026 16:58
@github-actions github-actions Bot added the feature New feature or request label Jun 6, 2026
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

No description provided.

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

토너먼트의 초대 코드 기반 참여 (홈 화면) 및 초대 링크 기반 참여 (초대 링크) 플로우를 API로 연동하고, 담기 화면에서 친구 초대 및 입금 마감 카운트다운을 추가하며, 결과 공유 시 플레이 링크를 생성·공유하도록 구현했습니다. 닉네임 중복 체크, 토너먼트 주최자 여부 판별, Web Share API 통합 등 관련 유틸리티도 함께 추가되었습니다.

Changes

소셜 토너먼트 API 연동 및 UI 보완

Layer / File(s) Summary
API Contracts and Type Definitions
apps/web/src/consts/api.ts, apps/web/src/app/tournament/[id]/_common/_types/tournamentResponse.ts, apps/web/src/types/user.ts, apps/web/src/app/tournament/join/_types/join.ts
엔드포인트 상수 추가(USERS_NICKNAME_CHECK, TOURNAMENT_* 5개) 및 토너먼트 응답 타입 확장(isOwner, pending.inviteCode/inviteExpiresAt, completed.hasGroupResult/playLinkExpiresAt)
Nickname Availability Check
apps/web/src/apis/getNicknameCheck.ts, apps/web/src/hooks/useGetNicknameCheck.ts
서버/클라이언트 환경 분기를 통한 닉네임 중복 체크 API 호출 및 300ms 디바운스 기반 실시간 검증 훅 구현
Invite Code Input Flow (Home Screen)
apps/web/src/app/home/_components/InviteTournamentButton.tsx, apps/web/src/app/home/_components/invite-code-dialog/InviteCodeDialog.tsx, apps/web/src/app/home/_components/invite-code-dialog/InvalidCodeDialog.tsx, apps/web/src/app/tournament/join/_utils/verifyInviteCode.ts
초대 코드 입력 (6자리, 대문자화), 클라이언트 형식 검증, getInvitePreviewByCode 뮤테이션, 성공/실패 다이얼로그 분기
Invite Link Preview and Guest Join
apps/web/src/app/tournament/join/[id]/page.tsx, apps/web/src/app/tournament/join/[id]/_components/JoinPreviewClient.tsx, apps/web/src/app/tournament/join/_apis/getInvitePreview.ts, apps/web/src/app/tournament/join/_apis/postJoinGuest.ts, apps/web/src/app/tournament/join/_hooks/useGetInvitePreview.ts, apps/web/src/app/tournament/join/_hooks/usePostJoinGuest.ts
초대 링크 기반 토너먼트 미리보기 및 게스트 닉네임 설정 후 참여, HydrationBoundary 활용 서버 prefetch
Tournament Create Screen: Countdown, Participants, and Dialogs
apps/web/src/app/tournament/[id]/create/_components/TournamentCreateClient.tsx, apps/web/src/app/tournament/[id]/create/_components/deposit-countdown/DepositCountdown.tsx, apps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantPanel.tsx, apps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantChip.tsx, apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriendsDialog.tsx, apps/web/src/app/tournament/[id]/create/_components/tournament-header/TournamentHeader.tsx, apps/web/src/app/tournament/[id]/create/_components/tournament-header/ConfirmExitDialog.tsx, apps/web/src/app/tournament/[id]/create/_components/welcome-join-dialog/WelcomeJoinDialog.tsx, apps/web/src/app/tournament/[id]/create/_components/member-join-confirm-dialog/MemberJoinConfirmDialog.tsx
입금 마감 카운트다운, 참여자 패널 (펼침/접힘), 친구 초대 다이얼로그 (만료 시간 표시), 헤더 종료 확인, join 웰컴/확인 다이얼로그 및 sessionStorage 상태 관리
Basket Item Rendering with Deposit Closed State
apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasket.tsx, apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/EmptyBasketSlot.tsx, apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasketCarousel.tsx, apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket-status/TournamentItemBasketStatus.tsx
입금 마감 후 바구니 아이템 렌더링 비활성 및 스타일 변경, 빈 슬롯 조기 반환, 캐러셀 메모이제이션
Tournament Start Button with Confirmation Dialog
apps/web/src/app/tournament/[id]/create/_components/tournament-start-button/TournamentStartButton.tsx, apps/web/src/app/tournament/[id]/create/_components/tournament-start-button/ConfirmStartDialog.tsx
참여자/주최자 상태에 따른 버튼 동작 분기, 친구 존재 시 확인 다이얼로그, 입금 마감 조건 추가
Play Link Generation and Sharing
apps/web/src/app/tournament/[id]/result/_apis/postPlayLink.ts, apps/web/src/app/tournament/[id]/result/_hooks/usePostPlayLink.ts, apps/web/src/app/tournament/[id]/result/_components/plate-share-dialog/PlateShareDialog.tsx, apps/web/src/app/tournament/[id]/result/_components/ResultClient.tsx, apps/web/src/app/tournament/[id]/result/_components/ReceiptDrawMachine.tsx, apps/web/src/app/tournament/[id]/result/_components/ReceiptPaper.tsx
플레이 링크 생성 API 호출 및 공유 다이얼로그 (만료 시간 표시, 14일 후 만료 추정, share() 호출)
Web Share API Utility and Helper Functions
apps/web/src/utils/share.ts
Web Share API 우선 시도, AbortError 처리, url/text 폴백 기반 클립보드 복사
Mock Data and Utility Updates
apps/web/src/mocks/participants.ts, apps/web/src/app/tournament/join/_consts/randomNickname.ts, apps/web/src/app/tournament/join/_utils/joinSession.ts
참여자 목 데이터 추가, 랜덤 닉네임 후보 상수, sessionStorage 기반 환영/확인 페이로드 저장/소비
UI Styling and Minor Refactoring
apps/web/src/app/login/_components/LoginButtons.tsx, apps/web/src/app/login/_components/SocialLoginButton.tsx, apps/web/src/app/login/page.tsx, apps/web/src/app/tournament/[id]/create/by-wish/_components/WishSelectHeader.tsx, apps/web/src/app/tournament/[id]/create/_hooks/useGetTournamentItem.ts, apps/web/src/app/tournament/[id]/create/_hooks/usePostTournamentItemLink.ts, apps/web/src/app/tournament/[id]/create/page.tsx
약관 문구 레이아웃 정리, CVA 포맷 정규화, 임포트 순서 정리
Tournament Response Data and Cache Management
apps/web/src/app/tournament/[id]/create/_apis/getTournament.ts, apps/web/src/app/tournament/[id]/match/_hooks/useTournament.ts, apps/web/src/app/tournament/[id]/match/page.tsx, apps/web/src/app/tournament/[id]/create/_types/tournament.ts
토너먼트 응답에 주최자 정보 추가, 캐시 업데이터 함수 적용, Mock 참여자 병합 로직
Route Constants Update
apps/web/src/consts/route.ts
초대 코드 기반 경로 제거
API Integration Planning Documentation
docs/social-tournament-api-integration.md
미적용 엔드포인트 명세, 클라이언트 적용 계획, mock 정리 체크리스트, UI 누락 항목, 의존성 및 합의 필요 사항 정리

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • depromeet/PIKI-Client#157: 본 PR이 #157의 초대/참여/공유 플로우 API 연동 및 UI 보완 작업을 직접 구현합니다.

Possibly related PRs

  • depromeet/PIKI-Client#90: 토너먼트 결과 공유 관련 ReceiptDrawMachine, ReceiptPaper, tournament/result 컴포넌트/훅 변경이 겹쳐있습니다.
  • depromeet/PIKI-Client#133: 토너먼트 생성 화면 UI 흐름 (TournamentCreateClient.tsx, TournamentItemBasketCarousel.tsx) 변경이 겹쳐있습니다.
  • depromeet/PIKI-Client#111: 결과 공유 다이얼로그 관련 ReceiptDrawMachine.tsx, ReceiptPaper.tsx 렌더링/스타일 변경이 겹쳐있습니다.

Suggested labels

feature, WEB

Suggested reviewers

  • kanghaeun

🐰 토너먼트 초대부터 결과 공유까지
온전한 흐름을 완성했네요!
Web Share API로 친구들과 나눔도 쉬워졌어요~ ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/157-social-tournament-api-integration

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/app/tournament/[id]/match/_hooks/useTournament.ts (1)

15-15: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

import type 분리 필요

타입 전용 import와 값 import를 혼합하지 말고 분리해야 합니다. 코딩 가이드라인에 따라 import type을 별도로 분리하세요.

♻️ 제안하는 수정
-import { type TransitionStageT, getRoundLabel, getTransitionStage } from '../_consts/rounds';
+import type { TransitionStageT } from '../_consts/rounds';
+import { getRoundLabel, getTransitionStage } from '../_consts/rounds';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/`[id]/match/_hooks/useTournament.ts at line 15,
The import mixes a type with runtime values; split it into a type-only import
and a value import. Change the single line importing TransitionStageT,
getRoundLabel, and getTransitionStage from '../_consts/rounds' so that
TransitionStageT is imported with "import type { TransitionStageT } from
'../_consts/rounds'" and the runtime functions are imported with "import {
getRoundLabel, getTransitionStage } from '../_consts/rounds'"; update any
references that rely on TransitionStageT accordingly.

Source: Coding guidelines

🧹 Nitpick comments (9)
apps/web/src/app/tournament/[id]/result/_hooks/usePostPlayLink.ts (1)

6-8: ⚡ Quick win

mutation 성공 후 토너먼트 쿼리 무효화 검토 필요.

postPlayLink 성공 시 playLinkExpiresAt 필드가 갱신되므로, 토너먼트 데이터를 조회하는 다른 화면에서 최신 값을 보장하려면 onSuccessqueryClient.invalidateQueries(['tournament', tournamentId])를 추가하는 것이 좋습니다. 현재 PlateShareDialog가 로컬 상태로 관리하지만, 향후 다른 컴포넌트에서 서버 응답의 playLinkExpiresAt를 직접 참조할 경우 stale 데이터를 보게 될 수 있습니다.

♻️ 제안하는 개선안
+import { useQueryClient } from '`@tanstack/react-query`';
+
 import { postPlayLink } from '../_apis/postPlayLink';

 export const usePostPlayLink = (tournamentId: number) => {
+  const queryClient = useQueryClient();
+
   const { mutateAsync: postPlayLinkMutation, isPending: isPostPlayLinkPending } = useMutation({
     mutationFn: () => postPlayLink(tournamentId),
+    onSuccess: () => {
+      queryClient.invalidateQueries({ queryKey: ['tournament', tournamentId] });
+    },
   });

   return { postPlayLinkMutation, isPostPlayLinkPending };
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/`[id]/result/_hooks/usePostPlayLink.ts around
lines 6 - 8, The mutation created in usePostPlayLink (useMutation with
mutationFn: () => postPlayLink(tournamentId)) should invalidate the tournament
cache after success so other components see the updated playLinkExpiresAt; add
an onSuccess handler to postPlayLinkMutation that calls
queryClient.invalidateQueries(['tournament', tournamentId]) (or the equivalent
query key) and then proceed with any local state updates so the server response
is reflected across the app.
apps/web/src/utils/share.ts (1)

1-5: 💤 Low value

ShareDataT 타입을 export하면 재사용성이 향상됩니다.

share() 함수의 파라미터 타입인 ShareDataT를 export하지 않아, 소비자 코드에서 타입 추론에만 의존하거나 인라인으로 타입을 재정의해야 합니다. export하면 다른 모듈에서 share 호출 시 명시적으로 타입을 참조할 수 있어 가독성과 유지보수성이 개선됩니다.

♻️ 제안하는 개선안
-type ShareDataT = {
+export type ShareDataT = {
   title?: string;
   text?: string;
   url?: string;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/utils/share.ts` around lines 1 - 5, Export the ShareDataT type
so consumers can reference it explicitly: add an export to the type declaration
(export type ShareDataT = { title?: string; text?: string; url?: string; }) and
keep the share(data: ShareDataT) signature unchanged; update any modules that
need to import the type to import { ShareDataT } from this file so callers can
annotate parameters or reuse the type.
apps/web/src/app/tournament/join/[id]/_components/JoinPreviewClient.tsx (2)

90-90: 💤 Low value

조건부 props 전달 패턴 개선 고려.

Line 90에서 {...(!isNicknameAvailable ? { helperText: '...' } : {})}로 조건부 props를 전달하고 있습니다. 동작은 올바르지만, 더 명확한 패턴을 사용할 수 있습니다.

♻️ 더 명확한 조건부 props 패턴
           aria-invalid={!isNicknameAvailable}
-          {...(!isNicknameAvailable ? { helperText: '이미 사용 중인 닉네임이에요.' } : {})}
+          {...(!isNicknameAvailable && { helperText: '이미 사용 중인 닉네임이에요.' })}
         />

또는 더 명시적으로:

<Input
  // ...
  aria-invalid={!isNicknameAvailable}
  helperText={!isNicknameAvailable ? '이미 사용 중인 닉네임이에요.' : undefined}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/join/`[id]/_components/JoinPreviewClient.tsx at
line 90, The conditional props spread on the Input in JoinPreviewClient uses
{...(!isNicknameAvailable ? { helperText: '이미 사용 중인 닉네임이에요.' } : {})}, which is
less explicit; change it to pass helperText and aria-invalid explicitly (e.g.,
set helperText to the message when !isNicknameAvailable or undefined otherwise,
and set aria-invalid to !isNicknameAvailable) so the intention is clearer and
types are preserved in the Input component.

43-60: ⚡ Quick win

에러 처리에서 상세 정보 손실.

handleConfirm의 catch 블록(Line 57-59)에서 모든 에러를 동일한 일반 메시지로 처리하고 있습니다. API 응답에 포함된 구체적인 에러 정보(detail 필드 등)를 사용자에게 전달하면 더 나은 사용자 경험을 제공할 수 있습니다.

♻️ 에러 메시지 개선 제안
     } catch (error) {
-      toast.warning('참여에 실패했어요. 잠시 후 다시 시도해주세요.');
+      const message = 
+        error instanceof Error 
+          ? error.message 
+          : '참여에 실패했어요. 잠시 후 다시 시도해주세요.';
+      toast.warning(message);
     }

또는 API 응답 구조에 따라:

} catch (error) {
  const message = (error as any)?.detail ?? '참여에 실패했어요. 잠시 후 다시 시도해주세요.';
  toast.warning(message);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/join/`[id]/_components/JoinPreviewClient.tsx
around lines 43 - 60, In handleConfirm, improve error handling in the catch
block for the postJoinGuestMutation call by capturing the thrown error,
extracting a specific message (e.g., error.detail or error.message) with a
sensible fallback ('참여에 실패했어요. 잠시 후 다시 시도해주세요.'), and passing that string to
toast.warning; also consider logging the full error for debugging (e.g.,
console.error or a logger) so you retain the raw response while showing the user
a clearer message. Use the existing function names (handleConfirm,
postJoinGuestMutation, setJoinWelcome) to locate the change.
apps/web/src/app/tournament/join/_consts/randomNickname.ts (1)

20-20: 💤 Low value

Non-null assertion 대신 타입 안전 방식 고려.

RANDOM_NICKNAMES[0]!에서 non-null assertion(!)을 사용하고 있습니다. 배열이 비어있지 않음을 보장하지만, 더 타입 안전한 방식으로 개선할 수 있습니다.

♻️ 타입 안전성을 높이는 대안
-export const DEFAULT_RANDOM_NICKNAME = RANDOM_NICKNAMES[0]!;
+export const DEFAULT_RANDOM_NICKNAME: RandomNicknameT = RANDOM_NICKNAMES[0]!;

또는 런타임 검증을 추가:

const first = RANDOM_NICKNAMES[0];
if (!first) throw new Error('RANDOM_NICKNAMES must not be empty');
export const DEFAULT_RANDOM_NICKNAME = first;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/join/_consts/randomNickname.ts` at line 20,
DEFAULT_RANDOM_NICKNAME uses a non-null assertion on RANDOM_NICKNAMES[0] which
is unsafe; replace it with a type-safe approach by reading RANDOM_NICKNAMES[0]
into a local const (e.g., first) and either (a) perform a runtime check and
throw a clear error if first is undefined, or (b) ensure RANDOM_NICKNAMES is
typed as a non-empty tuple so the compiler guarantees presence; update the
export to use the checked local variable instead of RANDOM_NICKNAMES[0]!. Keep
references to DEFAULT_RANDOM_NICKNAME and RANDOM_NICKNAMES when making the
change.
apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriendsDialog.tsx (1)

119-121: ⚡ Quick win

초대 URL이 없을 때 버튼을 비활성화하세요.

현재 inviteUrl이 없어도 버튼이 활성화되어 있어 사용자가 클릭 시 아무 동작도 일어나지 않습니다. disabled 속성을 추가해 명확한 UX를 제공하는 것이 좋습니다.

♻️ 제안하는 수정안
-          <Button size="lg" variant="primary" className="w-full" onClick={handleSendInviteLink}>
+          <Button 
+            size="lg" 
+            variant="primary" 
+            className="w-full" 
+            onClick={handleSendInviteLink}
+            disabled={!inviteUrl}
+          >
             초대 링크 보내기
           </Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/tournament/`[id]/create/_components/invite-friends/InviteFriendsDialog.tsx
around lines 119 - 121, The send-invite button in InviteFriendsDialog is
clickable even when inviteUrl is empty; update the Button in
InviteFriendsDialog.tsx to set disabled={ !inviteUrl } (or a boolean derived
from inviteUrl) so the Button rendered by the component is disabled when
inviteUrl is falsy, and ensure handleSendInviteLink is only called when enabled
(no-op guard is optional inside handleSendInviteLink).
apps/web/src/app/tournament/[id]/create/_components/TournamentCreateClient.tsx (1)

11-16: ⚡ Quick win

타입 전용 import에 import type 사용 권장

JoinConfirmPayloadTJoinWelcomePayloadT는 타입 선언으로만 사용되므로 import type으로 분리하는 것이 코딩 가이드라인에 부합합니다.

♻️ 제안 수정
-import {
-  type JoinConfirmPayloadT,
-  type JoinWelcomePayloadT,
-  consumeJoinConfirmFor,
-  consumeJoinWelcomeFor,
-} from '../../../join/_utils/joinSession';
+import type { JoinConfirmPayloadT, JoinWelcomePayloadT } from '../../../join/_utils/joinSession';
+import { consumeJoinConfirmFor, consumeJoinWelcomeFor } from '../../../join/_utils/joinSession';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/tournament/`[id]/create/_components/TournamentCreateClient.tsx
around lines 11 - 16, The two symbols JoinConfirmPayloadT and
JoinWelcomePayloadT are used only as types; change the import to use a type-only
import for them and keep the runtime imports for consumeJoinConfirmFor and
consumeJoinWelcomeFor as regular imports (e.g., add an `import type {
JoinConfirmPayloadT, JoinWelcomePayloadT } from '...';` and a separate `import {
consumeJoinConfirmFor, consumeJoinWelcomeFor } from '...';`) so the
compiler/tree-shaker knows they are types and runtime values are imported
correctly.

Source: Coding guidelines

apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/EmptyBasketSlot.tsx (1)

11-14: ⚡ Quick win

빈 슬롯 렌더링 확인 필요.

isDepositClosedtrue일 때 완전히 빈 div만 렌더링됩니다. TODO 주석에서 디자이너의 SVG로 교체 예정임을 명시하고 있지만, 현재 상태에서는 사용자에게 빈 공간만 보여 시각적 일관성이 떨어질 수 있습니다.

디자인 에셋이 준비되기 전까지 임시로라도 placeholder 스타일(예: 회색 배경 또는 아웃라인)을 추가하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/tournament/`[id]/create/_components/tournament-item-basket/EmptyBasketSlot.tsx
around lines 11 - 14, EmptyBasketSlot currently returns an empty div when
isDepositClosed is true which renders as blank space; update the EmptyBasketSlot
component to render a visible placeholder (e.g., add a neutral background,
dashed border or subtle outline and rounded corners) instead of a fully empty
element so users see a clear empty-slot affordance; locate the isDepositClosed
branch in EmptyBasketSlot and change the returned element to include placeholder
styling classes (and an accessible aria-hidden/role or visually hidden label if
needed) so it visually matches other slots until the designer SVG is available.
apps/web/src/app/tournament/[id]/create/_components/tournament-start-button/TournamentStartButton.tsx (1)

31-31: ⚡ Quick win

useGetTournament 호출 중복 가능성 검토.

TournamentStartButton 내부에서 useGetTournament를 호출해 hasFriends 계산에 사용하고 있습니다. 그러나 부모 컴포넌트(TournamentCreateClient)도 동일한 훅을 이미 호출하고 있어 데이터가 중복으로 페칭될 수 있습니다.

참가자 수 정보를 props로 전달받는 방식으로 리팩터링하면 불필요한 중복 호출을 방지하고 데이터 흐름을 명확히 할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/tournament/`[id]/create/_components/tournament-start-button/TournamentStartButton.tsx
at line 31, TournamentStartButton currently calls useGetTournament itself (const
{ tournamentData } = useGetTournament(...)), causing duplicate fetches because
TournamentCreateClient already fetches the same data; remove the
useGetTournament call from TournamentStartButton and instead accept the minimal
derived prop(s) needed (e.g., participantsCount or hasFriends boolean) via its
props, compute hasFriends from that prop inside TournamentStartButton, and
update TournamentCreateClient to pass the participantsCount/hasFriends it
already fetched to TournamentStartButton; reference the useGetTournament call
and the TournamentStartButton/TournamentCreateClient components when making this
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/app/home/_components/invite-code-dialog/InviteCodeDialog.tsx`:
- Around line 13-17: The imports in InviteCodeDialog.tsx use deep relative
paths; update them to use the project's absolute alias so maintenance is easier:
replace the relative imports for getInvitePreviewByCode and for CODE_LENGTH and
isValidInviteCodeFormat with their equivalent imports using the "`@/`..." alias
(e.g. import getInvitePreviewByCode from '`@/`...') so the module specifiers use
the `@/` prefix instead of ../../../; keep the imported symbol names and paths
matching the original modules and run the build to verify no path resolution
errors.
- Around line 38-48: The onError handler currently maps every error to
setIsInvalidDialogOpen(true); update the logic in the onError callback so that
only Axios errors with status 400 or 409 open the InvalidCodeDialog (keep
isAxiosError and the status check), and for all other errors show a user-facing
toast (or toastError) with a generic "Network/server error, please try again"
message and optionally log the full error for debugging instead of opening the
invalid-code dialog; leave setIsInvalidDialogOpen usage only in the 400/409
branch.

In `@apps/web/src/app/tournament/`[id]/create/_hooks/useCountdown.ts:
- Line 18: The current check uses typeof deadline === 'object' which can be true
for null, arrays or plain objects; update the logic in useCountdown to robustly
detect Date instances (e.g. use deadline instanceof Date and also ensure
deadline !== null) before calling deadline.getTime(), and fall back to
parsing/constructing a Date from string/number inputs for the else branch;
adjust the code paths around the target variable so getTime() is only called on
a confirmed Date and invalid/unsupported deadline values are handled or
defaulted safely.

In
`@apps/web/src/app/tournament/`[id]/result/_components/plate-share-dialog/PlateShareDialog.tsx:
- Around line 97-100: The "14일 후 마감" label in PlateShareDialog is hardcoded and
can disagree with the actual playLinkExpiresAt value; update PlateShareDialog to
compute the remaining days from the playLinkExpiresAt state (like
InviteFriendsDialog does) and set expiresLabel dynamically instead of the
literal "14일 후 마감"; locate the expiresLabel usage and the playLinkExpiresAt
state in PlateShareDialog, calculate daysRemaining = ceil((playLinkExpiresAt -
now) / 1 day) (handle past/invalid dates), and render a localized string such as
`${daysRemaining}일 후 마감` or a fallback when playLinkExpiresAt is missing.

In `@apps/web/src/app/tournament/join/_apis/getInvitePreview.ts`:
- Around line 12-16: The query key used in getInvitePreview is inconsistent with
the comment: rename the query param to match the documented/expected key by
sending { code: inviteCode } instead of { inviteCode } (and update local
variable names/types if needed), and apply the same change for the other params
construction later in the function (the other occurrence mentioned in the
review) so both request payload and documentation/types/contracts are aligned
with the backend.

In `@apps/web/src/app/tournament/join/_apis/getInvitePreviewByCode.ts`:
- Line 5: 현재 파일 getInvitePreviewByCode.ts의 상대 경로 import for
GetInvitePreviewResponseT should be changed to the project absolute alias;
replace the relative import "import type { GetInvitePreviewResponseT } from
'../_types/join';" with the equivalent alias form using "`@/`..." so that
GetInvitePreviewResponseT is imported via the absolute path (e.g., import type {
GetInvitePreviewResponseT } from '`@/app/tournament/join/_types/join`';) — update
the import in the getInvitePreviewByCode.ts module to use the `@/`* alias.

In `@apps/web/src/app/tournament/join/_hooks/useGetInvitePreview.ts`:
- Around line 5-9: The hook useGetInvitePreview currently only accepts
tournamentId and calls getInvitePreview via useSuspenseQuery with queryKey
['invitePreview', tournamentId], losing inviteCode-based validation and cache
separation; update the useGetInvitePreview signature to accept (tournamentId:
number, inviteCode?: string), include inviteCode when calling getInvitePreview,
and add inviteCode into the queryKey (e.g. ['invitePreview', tournamentId,
inviteCode]) so useSuspenseQuery and getInvitePreview receive and cache results
per inviteCode.

In `@apps/web/src/app/tournament/join/`[id]/_components/JoinPreviewClient.tsx:
- Around line 3-17: The imports at the top of JoinPreviewClient.tsx are grouped
correctly by external → absolute (`@/`) → relative (.//../../) but lack blank
lines between groups; update the import block so there is a single empty line
separating external imports (e.g., 'next/navigation', 'react', 'sonner') from
absolute imports (anything starting with '`@/`': EditIconFill, Button, Header,
Input, Spinner, useGetNicknameCheck) and another empty line separating those
from relative imports (DEFAULT_RANDOM_NICKNAME, useGetInvitePreview,
usePostJoinGuest, setJoinWelcome) to satisfy the project's import-group spacing
rule.

In `@apps/web/src/app/tournament/join/`[id]/page.tsx:
- Around line 10-24: TournamentJoinPage currently only reads params and ignores
the query string code so the invite verification flow is skipped; update the
component signature to accept searchParams, extract const { code } =
searchParams (or undefined), and pass that code into
getInvitePreview(tournamentId, code) and include it in the prefetch queryKey
(e.g. ['invitePreview', tournamentId, code]) so the API receives the code and
caching is keyed by the code; keep parseIdParam and the notFound() behavior
unchanged.

In `@docs/social-tournament-api-integration.md`:
- Around line 11-27: Update the "공통 응답 래퍼" section to match the real
ApiResponseBody contract used across the codebase: replace the example payload
to include status and code fields and rename pageResponse to pageInfo;
explicitly document that clients/axios interceptors should unwrap
response.data.data, that client API functions consume response.data.data, and
that on failure data is null while status/detail/code (and validation errors
under errors) contain failure details; update references to ApiResponseBody,
pageResponse→pageInfo, and the examples of status/code usage accordingly.

---

Outside diff comments:
In `@apps/web/src/app/tournament/`[id]/match/_hooks/useTournament.ts:
- Line 15: The import mixes a type with runtime values; split it into a
type-only import and a value import. Change the single line importing
TransitionStageT, getRoundLabel, and getTransitionStage from '../_consts/rounds'
so that TransitionStageT is imported with "import type { TransitionStageT } from
'../_consts/rounds'" and the runtime functions are imported with "import {
getRoundLabel, getTransitionStage } from '../_consts/rounds'"; update any
references that rely on TransitionStageT accordingly.

---

Nitpick comments:
In
`@apps/web/src/app/tournament/`[id]/create/_components/invite-friends/InviteFriendsDialog.tsx:
- Around line 119-121: The send-invite button in InviteFriendsDialog is
clickable even when inviteUrl is empty; update the Button in
InviteFriendsDialog.tsx to set disabled={ !inviteUrl } (or a boolean derived
from inviteUrl) so the Button rendered by the component is disabled when
inviteUrl is falsy, and ensure handleSendInviteLink is only called when enabled
(no-op guard is optional inside handleSendInviteLink).

In
`@apps/web/src/app/tournament/`[id]/create/_components/tournament-item-basket/EmptyBasketSlot.tsx:
- Around line 11-14: EmptyBasketSlot currently returns an empty div when
isDepositClosed is true which renders as blank space; update the EmptyBasketSlot
component to render a visible placeholder (e.g., add a neutral background,
dashed border or subtle outline and rounded corners) instead of a fully empty
element so users see a clear empty-slot affordance; locate the isDepositClosed
branch in EmptyBasketSlot and change the returned element to include placeholder
styling classes (and an accessible aria-hidden/role or visually hidden label if
needed) so it visually matches other slots until the designer SVG is available.

In
`@apps/web/src/app/tournament/`[id]/create/_components/tournament-start-button/TournamentStartButton.tsx:
- Line 31: TournamentStartButton currently calls useGetTournament itself (const
{ tournamentData } = useGetTournament(...)), causing duplicate fetches because
TournamentCreateClient already fetches the same data; remove the
useGetTournament call from TournamentStartButton and instead accept the minimal
derived prop(s) needed (e.g., participantsCount or hasFriends boolean) via its
props, compute hasFriends from that prop inside TournamentStartButton, and
update TournamentCreateClient to pass the participantsCount/hasFriends it
already fetched to TournamentStartButton; reference the useGetTournament call
and the TournamentStartButton/TournamentCreateClient components when making this
change.

In
`@apps/web/src/app/tournament/`[id]/create/_components/TournamentCreateClient.tsx:
- Around line 11-16: The two symbols JoinConfirmPayloadT and JoinWelcomePayloadT
are used only as types; change the import to use a type-only import for them and
keep the runtime imports for consumeJoinConfirmFor and consumeJoinWelcomeFor as
regular imports (e.g., add an `import type { JoinConfirmPayloadT,
JoinWelcomePayloadT } from '...';` and a separate `import {
consumeJoinConfirmFor, consumeJoinWelcomeFor } from '...';`) so the
compiler/tree-shaker knows they are types and runtime values are imported
correctly.

In `@apps/web/src/app/tournament/`[id]/result/_hooks/usePostPlayLink.ts:
- Around line 6-8: The mutation created in usePostPlayLink (useMutation with
mutationFn: () => postPlayLink(tournamentId)) should invalidate the tournament
cache after success so other components see the updated playLinkExpiresAt; add
an onSuccess handler to postPlayLinkMutation that calls
queryClient.invalidateQueries(['tournament', tournamentId]) (or the equivalent
query key) and then proceed with any local state updates so the server response
is reflected across the app.

In `@apps/web/src/app/tournament/join/_consts/randomNickname.ts`:
- Line 20: DEFAULT_RANDOM_NICKNAME uses a non-null assertion on
RANDOM_NICKNAMES[0] which is unsafe; replace it with a type-safe approach by
reading RANDOM_NICKNAMES[0] into a local const (e.g., first) and either (a)
perform a runtime check and throw a clear error if first is undefined, or (b)
ensure RANDOM_NICKNAMES is typed as a non-empty tuple so the compiler guarantees
presence; update the export to use the checked local variable instead of
RANDOM_NICKNAMES[0]!. Keep references to DEFAULT_RANDOM_NICKNAME and
RANDOM_NICKNAMES when making the change.

In `@apps/web/src/app/tournament/join/`[id]/_components/JoinPreviewClient.tsx:
- Line 90: The conditional props spread on the Input in JoinPreviewClient uses
{...(!isNicknameAvailable ? { helperText: '이미 사용 중인 닉네임이에요.' } : {})}, which is
less explicit; change it to pass helperText and aria-invalid explicitly (e.g.,
set helperText to the message when !isNicknameAvailable or undefined otherwise,
and set aria-invalid to !isNicknameAvailable) so the intention is clearer and
types are preserved in the Input component.
- Around line 43-60: In handleConfirm, improve error handling in the catch block
for the postJoinGuestMutation call by capturing the thrown error, extracting a
specific message (e.g., error.detail or error.message) with a sensible fallback
('참여에 실패했어요. 잠시 후 다시 시도해주세요.'), and passing that string to toast.warning; also
consider logging the full error for debugging (e.g., console.error or a logger)
so you retain the raw response while showing the user a clearer message. Use the
existing function names (handleConfirm, postJoinGuestMutation, setJoinWelcome)
to locate the change.

In `@apps/web/src/utils/share.ts`:
- Around line 1-5: Export the ShareDataT type so consumers can reference it
explicitly: add an export to the type declaration (export type ShareDataT = {
title?: string; text?: string; url?: string; }) and keep the share(data:
ShareDataT) signature unchanged; update any modules that need to import the type
to import { ShareDataT } from this file so callers can annotate parameters or
reuse the type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba2a67f9-40a0-4d9b-93bf-417a614f3dc3

📥 Commits

Reviewing files that changed from the base of the PR and between d36ef34 and d970c4a.

⛔ Files ignored due to path filters (4)
  • apps/web/src/app/home/_assets/sad-face.svg is excluded by !**/*.svg
  • apps/web/src/app/tournament/[id]/create/_assets/confirm-exit-basket.svg is excluded by !**/*.svg
  • apps/web/src/app/tournament/[id]/create/_assets/confirm-start-face.svg is excluded by !**/*.svg
  • apps/web/src/assets/icons/fill/timer.svg is excluded by !**/*.svg
📒 Files selected for processing (58)
  • apps/web/src/apis/getNicknameCheck.ts
  • apps/web/src/app/home/_components/InviteTournamentButton.tsx
  • apps/web/src/app/home/_components/invite-code-dialog/InvalidCodeDialog.tsx
  • apps/web/src/app/home/_components/invite-code-dialog/InviteCodeDialog.tsx
  • apps/web/src/app/home/page.tsx
  • apps/web/src/app/login/_components/LoginButtons.tsx
  • apps/web/src/app/login/_components/SocialLoginButton.tsx
  • apps/web/src/app/login/page.tsx
  • apps/web/src/app/tournament/[id]/_common/_types/tournamentResponse.ts
  • apps/web/src/app/tournament/[id]/create/_apis/getTournament.ts
  • apps/web/src/app/tournament/[id]/create/_components/TournamentCreateClient.tsx
  • apps/web/src/app/tournament/[id]/create/_components/deposit-countdown/DepositCountdown.tsx
  • apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriends.tsx
  • apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriendsDialog.tsx
  • apps/web/src/app/tournament/[id]/create/_components/member-join-confirm-dialog/MemberJoinConfirmDialog.tsx
  • apps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantChip.tsx
  • apps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantPanel.tsx
  • apps/web/src/app/tournament/[id]/create/_components/tournament-header/ConfirmExitDialog.tsx
  • apps/web/src/app/tournament/[id]/create/_components/tournament-header/TournamentHeader.tsx
  • apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket-status/TournamentItemBasketStatus.tsx
  • apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/EmptyBasketSlot.tsx
  • apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasket.tsx
  • apps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasketCarousel.tsx
  • apps/web/src/app/tournament/[id]/create/_components/tournament-start-button/ConfirmStartDialog.tsx
  • apps/web/src/app/tournament/[id]/create/_components/tournament-start-button/TournamentStartButton.tsx
  • apps/web/src/app/tournament/[id]/create/_components/welcome-join-dialog/WelcomeJoinDialog.tsx
  • apps/web/src/app/tournament/[id]/create/_hooks/useCountdown.ts
  • apps/web/src/app/tournament/[id]/create/_hooks/useGetTournamentItem.ts
  • apps/web/src/app/tournament/[id]/create/_hooks/usePostTournamentItemLink.ts
  • apps/web/src/app/tournament/[id]/create/_types/tournament.ts
  • apps/web/src/app/tournament/[id]/create/by-wish/_components/WishSelectHeader.tsx
  • apps/web/src/app/tournament/[id]/create/page.tsx
  • apps/web/src/app/tournament/[id]/match/_hooks/useTournament.ts
  • apps/web/src/app/tournament/[id]/match/page.tsx
  • apps/web/src/app/tournament/[id]/result/_apis/postPlayLink.ts
  • apps/web/src/app/tournament/[id]/result/_components/ReceiptDrawMachine.tsx
  • apps/web/src/app/tournament/[id]/result/_components/ReceiptPaper.tsx
  • apps/web/src/app/tournament/[id]/result/_components/ResultClient.tsx
  • apps/web/src/app/tournament/[id]/result/_components/plate-share-dialog/PlateShareDialog.tsx
  • apps/web/src/app/tournament/[id]/result/_hooks/usePostPlayLink.ts
  • apps/web/src/app/tournament/join/[id]/_components/JoinPreviewClient.tsx
  • apps/web/src/app/tournament/join/[id]/page.tsx
  • apps/web/src/app/tournament/join/_apis/getInvitePreview.ts
  • apps/web/src/app/tournament/join/_apis/getInvitePreviewByCode.ts
  • apps/web/src/app/tournament/join/_apis/postJoinGuest.ts
  • apps/web/src/app/tournament/join/_consts/randomNickname.ts
  • apps/web/src/app/tournament/join/_hooks/useGetInvitePreview.ts
  • apps/web/src/app/tournament/join/_hooks/usePostJoinGuest.ts
  • apps/web/src/app/tournament/join/_types/join.ts
  • apps/web/src/app/tournament/join/_utils/joinSession.ts
  • apps/web/src/app/tournament/join/_utils/verifyInviteCode.ts
  • apps/web/src/consts/api.ts
  • apps/web/src/consts/route.ts
  • apps/web/src/hooks/useGetNicknameCheck.ts
  • apps/web/src/mocks/participants.ts
  • apps/web/src/types/user.ts
  • apps/web/src/utils/share.ts
  • docs/social-tournament-api-integration.md
💤 Files with no reviewable changes (2)
  • apps/web/src/consts/route.ts
  • apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriends.tsx

Comment on lines +13 to +17
import { getInvitePreviewByCode } from '../../../tournament/join/_apis/getInvitePreviewByCode';
import {
CODE_LENGTH,
isValidInviteCodeFormat,
} from '../../../tournament/join/_utils/verifyInviteCode';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

상대 경로를 절대 경로로 변경하세요.

코딩 가이드라인에 따라 apps/web/src/**/*.{ts,tsx} 파일에서는 상대 경로 대신 절대 경로 별칭 @/*를 사용해야 합니다. 깊은 상대 경로(../../../)는 유지보수성을 저하시킵니다.

♻️ 제안 수정
-import { getInvitePreviewByCode } from '../../../tournament/join/_apis/getInvitePreviewByCode';
-import {
-  CODE_LENGTH,
-  isValidInviteCodeFormat,
-} from '../../../tournament/join/_utils/verifyInviteCode';
+import { getInvitePreviewByCode } from '`@/app/tournament/join/_apis/getInvitePreviewByCode`';
+import {
+  CODE_LENGTH,
+  isValidInviteCodeFormat,
+} from '`@/app/tournament/join/_utils/verifyInviteCode`';
 import InvalidCodeDialog from './InvalidCodeDialog';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { getInvitePreviewByCode } from '../../../tournament/join/_apis/getInvitePreviewByCode';
import {
CODE_LENGTH,
isValidInviteCodeFormat,
} from '../../../tournament/join/_utils/verifyInviteCode';
import { getInvitePreviewByCode } from '`@/app/tournament/join/_apis/getInvitePreviewByCode`';
import {
CODE_LENGTH,
isValidInviteCodeFormat,
} from '`@/app/tournament/join/_utils/verifyInviteCode`';
import InvalidCodeDialog from './InvalidCodeDialog';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/home/_components/invite-code-dialog/InviteCodeDialog.tsx`
around lines 13 - 17, The imports in InviteCodeDialog.tsx use deep relative
paths; update them to use the project's absolute alias so maintenance is easier:
replace the relative imports for getInvitePreviewByCode and for CODE_LENGTH and
isValidInviteCodeFormat with their equivalent imports using the "`@/`..." alias
(e.g. import getInvitePreviewByCode from '`@/`...') so the module specifiers use
the `@/` prefix instead of ../../../; keep the imported symbol names and paths
matching the original modules and run the build to verify no path resolution
errors.

Source: Coding guidelines

Comment on lines +38 to +48
onError: error => {
// 400: 코드 없음 / 409: 만료 — 둘 다 사용자에게 InvalidCodeDialog 안내
if (isAxiosError(error)) {
const status = error.response?.status;
if (status === 400 || status === 409) {
setIsInvalidDialogOpen(true);
return;
}
}
setIsInvalidDialogOpen(true);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

네트워크/서버 오류 시 더 적절한 에러 메시지를 제공하세요.

현재 Line 47에서 모든 예외(네트워크 오류, 500 에러 등)를 InvalidCodeDialog로 처리하고 있어, "코드가 유효하지 않아요"라는 메시지가 실제 오류 원인과 맞지 않을 수 있습니다. 400/409가 아닌 경우 toast로 일반적인 오류 안내를 표시하는 것이 사용자에게 더 정확한 정보를 전달합니다.

♻️ 제안 수정
+import { toast } from '`@/components/toast`';
+
 // ... (생략)

     onError: error => {
       // 400: 코드 없음 / 409: 만료 — 둘 다 사용자에게 InvalidCodeDialog 안내
       if (isAxiosError(error)) {
         const status = error.response?.status;
         if (status === 400 || status === 409) {
           setIsInvalidDialogOpen(true);
           return;
         }
       }
-      setIsInvalidDialogOpen(true);
+      toast.warning('일시적인 오류가 발생했어요. 잠시 후 다시 시도해주세요.');
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/home/_components/invite-code-dialog/InviteCodeDialog.tsx`
around lines 38 - 48, The onError handler currently maps every error to
setIsInvalidDialogOpen(true); update the logic in the onError callback so that
only Axios errors with status 400 or 409 open the InvalidCodeDialog (keep
isAxiosError and the status check), and for all other errors show a user-facing
toast (or toastError) with a generic "Network/server error, please try again"
message and optionally log the full error for debugging instead of opening the
invalid-code dialog; leave setIsInvalidDialogOpen usage only in the 400/409
branch.

* SSR/CSR 하이드레이션 불일치를 피하기 위해 마운트 후에만 계산.
*/
export const useCountdown = (deadline: Date | string | number) => {
const target = typeof deadline === 'object' ? deadline.getTime() : new Date(deadline).getTime();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

typeof deadline === 'object' 검사 개선 필요

현재 코드는 deadlineDate 객체일 때 getTime()을 호출하려 하지만, typeof deadline === 'object'null, 배열, 일반 객체도 true를 반환합니다. deadline이 예상치 못한 객체 타입일 경우 런타임 오류가 발생할 수 있습니다.

🛡️ 제안 수정
-  const target = typeof deadline === 'object' ? deadline.getTime() : new Date(deadline).getTime();
+  const target = deadline instanceof Date ? deadline.getTime() : new Date(deadline).getTime();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const target = typeof deadline === 'object' ? deadline.getTime() : new Date(deadline).getTime();
const target = deadline instanceof Date ? deadline.getTime() : new Date(deadline).getTime();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/`[id]/create/_hooks/useCountdown.ts at line 18,
The current check uses typeof deadline === 'object' which can be true for null,
arrays or plain objects; update the logic in useCountdown to robustly detect
Date instances (e.g. use deadline instanceof Date and also ensure deadline !==
null) before calling deadline.getTime(), and fall back to parsing/constructing a
Date from string/number inputs for the else branch; adjust the code paths around
the target variable so getTime() is only called on a confirmed Date and
invalid/unsupported deadline values are handled or defaulted safely.

Comment on lines +97 to +100
<div className="flex flex-col gap-0.5">
<p className="body-2-semibold text-text-accent">14일 후 마감</p>
<p className="heading-1 text-text-neutral-primary">{expiresLabel}</p>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

"14일 후 마감" 라벨을 실제 남은 기간 기반으로 동적 계산해야 일관성이 보장됩니다.

Line 98에서 "14일 후 마감" 텍스트가 하드코딩되어 있지만, playLinkExpiresAt 상태는 mutation 후 서버 응답으로 갱신되므로 실제 남은 기간과 불일치할 수 있습니다. 예를 들어 이미 생성된 링크가 7일 남았다면 "14일 후 마감"은 부정확합니다. InviteFriendsDialog(context snippet)처럼 playLinkExpiresAt에서 현재 시각 대비 남은 일수를 계산해 동적 라벨을 표시하는 것이 권장됩니다.

🛠️ 제안하는 수정안
+const formatRemainingDays = (expiresAt: string) => {
+  const now = new Date();
+  const expires = new Date(expiresAt);
+  const diffMs = expires.getTime() - now.getTime();
+  const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
+  return diffDays > 0 ? `${diffDays}일 후 마감` : '마감됨';
+};
+
 function PlateShareDialog({
   ...
 }: PlateShareDialogProps) {
   const [playLinkExpiresAt, setPlayLinkExpiresAt] = useState(
     initialPlayLinkExpiresAt ?? getEstimatedExpiresAt()
   );
   const { postPlayLinkMutation, isPostPlayLinkPending } = usePostPlayLink(tournamentId);

   const expiresLabel = formatExpiresLabel(new Date(playLinkExpiresAt));
+  const remainingLabel = formatRemainingDays(playLinkExpiresAt);

   ...
               <div className="flex flex-col gap-0.5">
-                <p className="body-2-semibold text-text-accent">14일 후 마감</p>
+                <p className="body-2-semibold text-text-accent">{remainingLabel}</p>
                 <p className="heading-1 text-text-neutral-primary">{expiresLabel}</p>
               </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/tournament/`[id]/result/_components/plate-share-dialog/PlateShareDialog.tsx
around lines 97 - 100, The "14일 후 마감" label in PlateShareDialog is hardcoded and
can disagree with the actual playLinkExpiresAt value; update PlateShareDialog to
compute the remaining days from the playLinkExpiresAt state (like
InviteFriendsDialog does) and set expiresLabel dynamically instead of the
literal "14일 후 마감"; locate the expiresLabel usage and the playLinkExpiresAt
state in PlateShareDialog, calculate daysRemaining = ceil((playLinkExpiresAt -
now) / 1 day) (handle past/invalid dates), and render a localized string such as
`${daysRemaining}일 후 마감` or a fallback when playLinkExpiresAt is missing.

Comment on lines +12 to +16
* 링크 직접 접근 경로 (`piki.today/invite/{tournamentId}?code=XXX`).
*/
export const getInvitePreview = async (tournamentId: number, inviteCode?: string) => {
const params = inviteCode ? { inviteCode } : {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

초대코드 쿼리 키 이름이 문서/주석과 불일치합니다.

주석은 ?code=XXX 경로를 명시하지만 실제 요청은 params: { inviteCode }로 전송합니다. 백엔드가 code만 받는 계약이면 링크 진입 시 검증이 우회되거나 실패합니다. code로 맞추거나 서버 계약을 주석/타입과 함께 일치시켜 주세요.

Also applies to: 19-21

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/join/_apis/getInvitePreview.ts` around lines 12 -
16, The query key used in getInvitePreview is inconsistent with the comment:
rename the query param to match the documented/expected key by sending { code:
inviteCode } instead of { inviteCode } (and update local variable names/types if
needed), and apply the same change for the other params construction later in
the function (the other occurrence mentioned in the review) so both request
payload and documentation/types/contracts are aligned with the backend.

import { ENDPOINTS } from '@/consts/api';
import type { ApiResponseT } from '@/types/api';

import type { GetInvitePreviewResponseT } from '../_types/join';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

상대 경로를 절대 경로로 변경하세요.

코딩 가이드라인에 따라 apps/web/src/**/*.{ts,tsx} 파일에서는 상대 경로 대신 절대 경로 별칭 @/*를 사용해야 합니다.

♻️ 제안 수정
-import type { GetInvitePreviewResponseT } from '../_types/join';
+import type { GetInvitePreviewResponseT } from '`@/app/tournament/join/_types/join`';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { GetInvitePreviewResponseT } from '../_types/join';
import type { GetInvitePreviewResponseT } from '`@/app/tournament/join/_types/join`';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/join/_apis/getInvitePreviewByCode.ts` at line 5,
현재 파일 getInvitePreviewByCode.ts의 상대 경로 import for GetInvitePreviewResponseT
should be changed to the project absolute alias; replace the relative import
"import type { GetInvitePreviewResponseT } from '../_types/join';" with the
equivalent alias form using "`@/`..." so that GetInvitePreviewResponseT is
imported via the absolute path (e.g., import type { GetInvitePreviewResponseT }
from '`@/app/tournament/join/_types/join`';) — update the import in the
getInvitePreviewByCode.ts module to use the `@/`* alias.

Source: Coding guidelines

Comment on lines +5 to +9
export const useGetInvitePreview = (tournamentId: number) => {
const { data: invitePreviewData } = useSuspenseQuery({
queryKey: ['invitePreview', tournamentId],
queryFn: () => getInvitePreview(tournamentId),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

훅에서 inviteCode를 받지 않아 초대링크 검증 컨텍스트를 잃습니다.

getInvitePreviewinviteCode를 지원하는데 이 훅은 tournamentId만 전달합니다. 결과적으로 코드별 검증/캐시 분리가 불가능합니다. 훅 시그니처와 queryKeyinviteCode까지 포함하도록 확장해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/join/_hooks/useGetInvitePreview.ts` around lines
5 - 9, The hook useGetInvitePreview currently only accepts tournamentId and
calls getInvitePreview via useSuspenseQuery with queryKey ['invitePreview',
tournamentId], losing inviteCode-based validation and cache separation; update
the useGetInvitePreview signature to accept (tournamentId: number, inviteCode?:
string), include inviteCode when calling getInvitePreview, and add inviteCode
into the queryKey (e.g. ['invitePreview', tournamentId, inviteCode]) so
useSuspenseQuery and getInvitePreview receive and cache results per inviteCode.

Comment on lines +3 to +17
import { notFound, useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'sonner';

import { EditIconFill } from '@/assets/icons/fill';
import Button from '@/components/button';
import { Header } from '@/components/header';
import Input from '@/components/input';
import Spinner from '@/components/spinner';
import { useGetNicknameCheck } from '@/hooks/useGetNicknameCheck';

import { DEFAULT_RANDOM_NICKNAME } from '../../_consts/randomNickname';
import { useGetInvitePreview } from '../../_hooks/useGetInvitePreview';
import { usePostJoinGuest } from '../../_hooks/usePostJoinGuest';
import { setJoinWelcome } from '../../_utils/joinSession';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Import 그룹 간 빈 줄 추가 필요.

Import 문이 external → absolute (@/) → relative (./) 순서로 정렬되어 있지만, 각 그룹 사이에 빈 줄이 없습니다. 코딩 가이드라인에 따라 그룹 간 자동 분리(빈 줄)가 필요합니다.

♻️ Import 그룹 분리 제안
 import { notFound, useRouter } from 'next/navigation';
 import { useState } from 'react';
 import { toast } from 'sonner';
+
 import { EditIconFill } from '`@/assets/icons/fill`';
 import Button from '`@/components/button`';
 import { Header } from '`@/components/header`';
 import Input from '`@/components/input`';
 import Spinner from '`@/components/spinner`';
 import { useGetNicknameCheck } from '`@/hooks/useGetNicknameCheck`';
+
 import { DEFAULT_RANDOM_NICKNAME } from '../../_consts/randomNickname';
 import { useGetInvitePreview } from '../../_hooks/useGetInvitePreview';
 import { usePostJoinGuest } from '../../_hooks/usePostJoinGuest';
 import { setJoinWelcome } from '../../_utils/joinSession';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { notFound, useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'sonner';
import { EditIconFill } from '@/assets/icons/fill';
import Button from '@/components/button';
import { Header } from '@/components/header';
import Input from '@/components/input';
import Spinner from '@/components/spinner';
import { useGetNicknameCheck } from '@/hooks/useGetNicknameCheck';
import { DEFAULT_RANDOM_NICKNAME } from '../../_consts/randomNickname';
import { useGetInvitePreview } from '../../_hooks/useGetInvitePreview';
import { usePostJoinGuest } from '../../_hooks/usePostJoinGuest';
import { setJoinWelcome } from '../../_utils/joinSession';
import { notFound, useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'sonner';
import { EditIconFill } from '`@/assets/icons/fill`';
import Button from '`@/components/button`';
import { Header } from '`@/components/header`';
import Input from '`@/components/input`';
import Spinner from '`@/components/spinner`';
import { useGetNicknameCheck } from '`@/hooks/useGetNicknameCheck`';
import { DEFAULT_RANDOM_NICKNAME } from '../../_consts/randomNickname';
import { useGetInvitePreview } from '../../_hooks/useGetInvitePreview';
import { usePostJoinGuest } from '../../_hooks/usePostJoinGuest';
import { setJoinWelcome } from '../../_utils/joinSession';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/join/`[id]/_components/JoinPreviewClient.tsx
around lines 3 - 17, The imports at the top of JoinPreviewClient.tsx are grouped
correctly by external → absolute (`@/`) → relative (.//../../) but lack blank
lines between groups; update the import block so there is a single empty line
separating external imports (e.g., 'next/navigation', 'react', 'sonner') from
absolute imports (anything starting with '`@/`': EditIconFill, Button, Header,
Input, Spinner, useGetNicknameCheck) and another empty line separating those
from relative imports (DEFAULT_RANDOM_NICKNAME, useGetInvitePreview,
usePostJoinGuest, setJoinWelcome) to satisfy the project's import-group spacing
rule.

Source: Coding guidelines

Comment on lines +10 to +24
type TournamentJoinPageProps = {
params: Promise<{ id: string }>;
};

async function TournamentJoinPage({ params }: TournamentJoinPageProps) {
const { id } = await params;
const tournamentId = parseIdParam(id);

if (tournamentId === null) notFound();

const queryClient = getQueryClient();
await queryClient.prefetchQuery({
queryKey: ['invitePreview', tournamentId],
queryFn: () => getInvitePreview(tournamentId),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

초대 링크의 code 쿼리를 읽지 않아 링크 검증 플로우가 누락됩니다.

현재는 params.id만 사용해 prefetch를 수행하므로 /tournament/join/{id}?code=...의 코드가 API에 전달되지 않습니다. searchParams.code를 파싱해 getInvitePreview(tournamentId, code)로 넘기고, queryKey에도 code를 포함해 주세요.

수정 예시
 type TournamentJoinPageProps = {
   params: Promise<{ id: string }>;
+  searchParams: Promise<{ code?: string }>;
 };

-async function TournamentJoinPage({ params }: TournamentJoinPageProps) {
+async function TournamentJoinPage({ params, searchParams }: TournamentJoinPageProps) {
   const { id } = await params;
+  const { code } = await searchParams;
   const tournamentId = parseIdParam(id);

   if (tournamentId === null) notFound();

   const queryClient = getQueryClient();
   await queryClient.prefetchQuery({
-    queryKey: ['invitePreview', tournamentId],
-    queryFn: () => getInvitePreview(tournamentId),
+    queryKey: ['invitePreview', tournamentId, code ?? null],
+    queryFn: () => getInvitePreview(tournamentId, code),
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/tournament/join/`[id]/page.tsx around lines 10 - 24,
TournamentJoinPage currently only reads params and ignores the query string code
so the invite verification flow is skipped; update the component signature to
accept searchParams, extract const { code } = searchParams (or undefined), and
pass that code into getInvitePreview(tournamentId, code) and include it in the
prefetch queryKey (e.g. ['invitePreview', tournamentId, code]) so the API
receives the code and caching is keyed by the code; keep parseIdParam and the
notFound() behavior unchanged.

Comment on lines +11 to +27
## 0. 공통 응답 래퍼

모든 응답은 `ApiResponseBody` 로 래핑됨.

```json
{
"data": {
/* 실제 응답 */
},
"detail": "정상적으로 처리되었습니다.",
"pageResponse": { "nextCursor": null, "hasNext": false }
}
```

- 클라이언트는 axios 인터셉터에서 `.data.data` 를 풀어 사용
- 실패 시 `data: null`, `detail`/`code` 에 사유

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

문서의 공통 응답 계약을 실제 타입/가이드와 일치시켜 주세요.

현재 공통 응답 예시가 data/detail/pageResponse 중심으로 작성되어 있는데, 코드베이스의 실제 계약은
{ status, data, detail, code }이며 페이징 키도 pageInfo입니다. 이 상태로 두면 후속 API 래퍼/훅 구현이 잘못될 가능성이 큽니다.

수정 제안
-## 0. 공통 응답 래퍼
+## 0. 공통 응답 래퍼

-모든 응답은 `ApiResponseBody` 로 래핑됨.
+모든 응답은 공통 래퍼로 내려오며, 클라이언트는 `status/data/detail/code`를 기준으로 처리한다.

 ```json
 {
+  "status": 200,
   "data": {
     /* 실제 응답 */
   },
   "detail": "정상적으로 처리되었습니다.",
-  "pageResponse": { "nextCursor": null, "hasNext": false }
+  "code": "SUCCESS",
+  "pageInfo": { "nextCursor": null, "hasNext": false }
 }

-- 클라이언트는 axios 인터셉터에서 .data.data 를 풀어 사용
-- 실패 시 data: null, detail/code 에 사유
+- 클라이언트 API 함수는 response.data.data를 언랩해 사용
+- 실패 시 data: null이며 status/detail/code(+ validation의 경우 errors)를 사용

</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
## 0. 공통 응답 래퍼

모든 응답은 공통 래퍼로 내려오며, 클라이언트는 `status/data/detail/code`를 기준으로 처리한다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/social-tournament-api-integration.md` around lines 11 - 27, Update the
"공통 응답 래퍼" section to match the real ApiResponseBody contract used across the
codebase: replace the example payload to include status and code fields and
rename pageResponse to pageInfo; explicitly document that clients/axios
interceptors should unwrap response.data.data, that client API functions consume
response.data.data, and that on failure data is null while status/detail/code
(and validation errors under errors) contain failure details; update references
to ApiResponseBody, pageResponse→pageInfo, and the examples of status/code usage
accordingly.

Source: Coding guidelines

@ychany

ychany commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@team 작업 PR 4개 (#142, #156, #159, #163) 를 squash merge 로 단일 PR #167 로 통합

@ychany ychany closed this Jun 9, 2026
ychany added a commit that referenced this pull request Jun 13, 2026
* Squashed commit of the following:

commit 52c225d
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 21:52:08 2026 +0900

    chore: format

commit 4f45210
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 21:31:28 2026 +0900

    refactor: 외부 라우트 import 를 절대경로로 변경

commit de4f8b4
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 19:00:18 2026 +0900

    fix: 친구 토너먼트 결과보기 카드 노출 조건 단순화 + 에러 안내

commit 143ca1e
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 18:42:01 2026 +0900

    fix: 플레이 링크 진입 후 CLONE 상태에 따라 라우팅 분기

commit e8cfd72
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 18:39:49 2026 +0900

    fix: 비로그인 진입 시 PlayClient 게스트 발급 회복 (401/400 둘 다 처리)

commit 3bcfaa3
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 18:31:34 2026 +0900

    fix: 플레이 링크 진입 시 토너먼트 상태에 따라 적절 화면 라우팅

commit 31cf810
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 18:28:26 2026 +0900

    fix: 결승 직후 토너먼트 캐시 시드 제거 (그룹 결과 카드 즉시 노출)

commit 8f6cc22
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 16:27:51 2026 +0900

    chore: from-play-link idempotent 동작 주석 갱신

commit 534dbed
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 14:07:22 2026 +0900

    fix: 결과 페이지에서 토너먼트 캐시를 권위 응답으로 강제 덮어쓰기

commit eec26db
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 04:26:29 2026 +0900

    chore : format

commit 7c0bb2b
Author: joyeongchan <tigerbone@naver.com>
Date:   Tue Jun 9 04:22:59 2026 +0900

    fix: 그룹 결과 카드를 ROOT 토너먼트에서만 노출

commit e813384
Author: joyeongchan <tigerbone@naver.com>
Date:   Mon Jun 8 20:56:22 2026 +0900

    feat: ownerStarted/isRoot 기반 참여자 대기·플레이 링크 공유 분기

commit 6d7805a
Author: joyeongchan <tigerbone@naver.com>
Date:   Mon Jun 8 20:43:08 2026 +0900

    fix: 친구 토너먼트 결과보기 카드에 아바타 그룹 추가 (시안 일치)

commit 02ae3d4
Author: joyeongchan <tigerbone@naver.com>
Date:   Mon Jun 8 20:36:39 2026 +0900

    refactor: PlateShareDialog 시안 일치 (스톱워치/체크리스트 제거)

commit 9810007
Author: joyeongchan <tigerbone@naver.com>
Date:   Mon Jun 8 19:24:41 2026 +0900

    feat: 참여자 토너먼트 진행 (CLONE 구조 대응)

commit 0990df0
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 05:18:02 2026 +0900

    fix: refresh/guest/play-link POST body를 빈 객체로 통일

commit c5e70d6
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 05:12:06 2026 +0900

    fix: 비회원 게스트 참여 시 inviteCode 전달

commit 9d55b3a
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 04:31:11 2026 +0900

    feat: 참여자 화면이 status 변화 감지해 자동 라우팅

commit bca0b40
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 04:08:54 2026 +0900

    fix: 결과 화면 하단 단일 CTA로 정리

commit f654e73
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 04:05:54 2026 +0900

    fix: body에 suppressHydrationWarning 추가 (확장 프로그램 대응)

commit bb1bc17
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 04:00:31 2026 +0900

    feat: 그룹 결과 화면 1st 강조 + Others 축약 형태로 재구성

commit 57d088b
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 03:55:22 2026 +0900

    fix: 회원 프로필 이미지 정사각형 컨테이너로 렌더링

commit 1189b79
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 03:53:27 2026 +0900

    fix: 비회원 참가자 프로필을 기본 SVG로 표시 (홈/토너먼트 일관 적용)

commit a73264d
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 03:51:38 2026 +0900

    fix: 비회원 참가자 프로필을 기본 SVG로 표시

commit a3b7ce1
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 03:43:26 2026 +0900

    feat: 친구 초대 링크 진입 라우트 추가

commit 5c13804
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 03:22:42 2026 +0900

    fix: 플레이 링크 공유 주최자 분기 + 기존 링크 재사용

commit 3726ce2
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 03:15:03 2026 +0900

    fix: WishGrid 리스트 항목에 누락된 key prop 추가

commit fd45030
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 03:12:06 2026 +0900

    fix: 참여자 mock 제거 및 친구 없을 때 카운트다운/시작 확인 분기

commit 4488d2c
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 03:03:53 2026 +0900

    fix: 주최자는 초대 만료와 무관하게 담기 가능하도록 수정

commit 7307ee7
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 02:35:08 2026 +0900

    feat: 그룹 결과 보기 화면 추가 및 결과 진입 카드 연동

commit ea2ddad
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 02:19:01 2026 +0900

    feat: 플레이 링크 진입 시 토너먼트 자동 복제 및 라우팅

commit d970c4a
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 01:43:30 2026 +0900

    chore: prettier

commit 8460097
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 01:27:39 2026 +0900

    feat: 플레이 링크 만료 시각 서버 응답 기반으로 표시

commit 9f032ee
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 01:21:19 2026 +0900

    feat: 초대 코드 검증 API 연동 및 미리보기 흐름 실 데이터화

commit 939b758
Author: joyeongchan <tigerbone@naver.com>
Date:   Sun Jun 7 00:03:57 2026 +0900

    refactor: 담기 마감을 초대 코드 만료 시간으로 통일

commit 03515cb
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 03:33:11 2026 +0900

    feat: 친구 초대 다이얼로그 동적 데이터 연동 (만료시간/링크)

commit 77a1806
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 03:25:24 2026 +0900

    feat: 비회원 게스트 토너먼트 참여 API 연동

commit 76a9a4e
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 03:16:41 2026 +0900

    feat: 플레이 링크 생성 API 연동 및 공유 URL 동적 구성

commit 3622c24
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 03:04:28 2026 +0900

    refactor: 주최자 분기를 tournamentData.isOwner로 통일

commit 9678e2a
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 02:56:28 2026 +0900

    feat: 닉네임 중복 체크 API 연동

commit 118eec3
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 02:36:24 2026 +0900

    docs: 소셜 토너먼트 API 연동 계획 문서 추가

commit 8b7d832
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 02:20:07 2026 +0900

    chore: 슬랙봇 임시완화 복구

commit c38bc5f
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 02:19:24 2026 +0900

    chore: 슬랙봇 누락

commit 0a4624d
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 02:14:13 2026 +0900

    chore: prettier format

commit 496717d
Merge: b2cb0f7 c858551
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 02:11:22 2026 +0900

    merge: dev 머지 + components/common 경로 리네임 반영

commit b2cb0f7
Author: joyeongchan <tigerbone@naver.com>
Date:   Sat Jun 6 01:55:52 2026 +0900

    chore: format

commit ae1c2b8
Author: joyeongchan <tigerbone@naver.com>
Date:   Thu Jun 4 04:12:40 2026 +0900

    feat: 홈 초대 코드 다이얼로그 + 형식/불일치 에러 분기 구현

commit 2a112f2
Author: joyeongchan <tigerbone@naver.com>
Date:   Thu Jun 4 02:54:50 2026 +0900

    feat: 참여자 패널 친구 유무에 따른 UI 분기

commit a2a860b
Author: joyeongchan <tigerbone@naver.com>
Date:   Thu Jun 4 02:29:23 2026 +0900

    chore: mock 담기 종료 시간 1분 조정

commit 0590e74
Author: joyeongchan <tigerbone@naver.com>
Date:   Thu Jun 4 02:22:10 2026 +0900

    feat: 초대 참여자에게 시작 버튼 비활성 + 안내 툴팁 표시

commit 43a8960
Author: joyeongchan <tigerbone@naver.com>
Date:   Thu Jun 4 02:05:46 2026 +0900

    fix: 담기 종료 후 슬롯 UI 정리 + 캐러셀 memo 처리

commit e3a7d8b
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 22:28:59 2026 +0900

    feat: 담기 종료 이후 상태별 UI 분기 처리

commit efb5d97
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 20:09:47 2026 +0900

    feat: 회원/비회원 초대 참여 분기 + 회원용 확인 다이얼로그 추가

commit 3839f69
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 20:01:12 2026 +0900

    fix: 초대 환영 바텀시트 대상 토너먼트에서만 노출

commit a5575e2
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 19:56:43 2026 +0900

    feat: 초대 참여 후 환영 바텀시트 자동 노출

commit 06088a1
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 19:45:29 2026 +0900

    fix: 초대 참여하기 화면 풀스크린 페이지로 변경

commit 1b11188
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 19:42:40 2026 +0900

    chore: 임시 mock 초대 코드 111111로 변경

commit 2ca2217
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 19:39:20 2026 +0900

    feat: 초대 참여 닉네임 확인 화면 UI 구현

commit 6eb6e7f
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 18:43:26 2026 +0900

    style: 초대 코드 화면 타이포·간격 시안 매칭

commit c012c39
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 18:41:25 2026 +0900

    fix: 홈 초대 토너먼트 입장 버튼 라우팅 연결

commit 46e1b95
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 18:37:11 2026 +0900

    fix: 초대 코드 에러 헬퍼 텍스트 디자인에 맞게 수정

commit 46f7399
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 17:51:21 2026 +0900

    refactor: 초대 코드 페이지 경로 invite → join 변경

commit 3b4f985
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 17:48:29 2026 +0900

    feat: 초대 코드 입력 화면 UI 구현

commit 3273be5
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 16:31:06 2026 +0900

    refactor: 익명 핸들러 분리 + 사용 안 하는 InviteFriends 제거

commit 8cf4580
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 04:26:02 2026 +0900

    chore: format

commit 229ab7a
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 04:25:00 2026 +0900

    feat: 결과 화면 플레이트 공유 다이얼로그 구현

commit 4841629
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 04:06:52 2026 +0900

    refactor: 친구 초대/참여자 패널 아이콘 색상 토큰 정리

commit c3ceccd
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 04:03:44 2026 +0900

    fix: 토너먼트 시작/뒤로가기 모달 아이콘 디자인 교체

commit 84c3dd4
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 03:59:28 2026 +0900

    fix: 친구 mock 머지 임계값을 2명 이상으로 조정

commit 3fd0f3c
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 03:44:21 2026 +0900

    refactor: 친구 포함 여부 판단을 tournamentData participants로 변경

commit c60ac1d
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 03:42:12 2026 +0900

    feat: 친구 포함 토너먼트 시작 확인 모달 추가

commit cf30ac6
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 03:34:03 2026 +0900

    feat: 후보 담기 마감 카운트다운 UI 구현

commit a71c8c3
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 03:21:44 2026 +0900

    feat: 토너먼트 참여자 패널 collapsed/expanded UI 구현

commit ecdfe2a
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 03:09:26 2026 +0900

    fix: 초대 링크 공유 토스트 메시지 통일

commit 11ecd7b
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 03:06:15 2026 +0900

    feat: 친구 초대 링크 공유 시트 연동 / 임시 더미 url

commit 6274dd7
Author: joyeongchan <tigerbone@naver.com>
Date:   Wed Jun 3 02:59:52 2026 +0900

    feat: 토너먼트 친구 초대 바텀시트 다이얼로그 구현

* fix: 코드 검증 중 입력 변경 시 race condition 방지

* fix: PlayClient 의 goToTournament 호출에 await 추가 (무한 로딩 방지)

* feat: 게스트도 친구 토너먼트 결과보기 (sourceTournamentId 활용)

* refactor: InviteClient 의 의미 없는 Axios 에러 분기 제거

* refactor: play _apis/_types 를 [id] 라우트 내부로 이동

* refactor: 토너먼트 응답 타입/훅 중복 제거 및 _common 으로 통합

* fix: 토너먼트 prefetch queryKey 의 id 타입 number 로 통일

* refactor: create 자식 컴포넌트의 중복 useGetTournament 호출 제거

* refactor: result 페이지 영수증 포맷 유틸을 _utils 로 추출

* chore: TournamentItemBasket 의 사용 변수에서 _ prefix 제거

* refactor: JoinPreviewClient 의 중복 null 체크 제거 및 tournamentId 타입 좁히기

* chore: format
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 소셜 토너먼트 API 연동 및 미구현 UI 보완

1 participant