feat: 소셜 토너먼트 API 연동 및 미구현 UI 보완#159
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No description provided. |
Walkthrough토너먼트의 초대 코드 기반 참여 (홈 화면) 및 초대 링크 기반 참여 (초대 링크) 플로우를 API로 연동하고, 담기 화면에서 친구 초대 및 입금 마감 카운트다운을 추가하며, 결과 공유 시 플레이 링크를 생성·공유하도록 구현했습니다. 닉네임 중복 체크, 토너먼트 주최자 여부 판별, Web Share API 통합 등 관련 유틸리티도 함께 추가되었습니다. Changes소셜 토너먼트 API 연동 및 UI 보완
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 winimport 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 winmutation 성공 후 토너먼트 쿼리 무효화 검토 필요.
postPlayLink성공 시playLinkExpiresAt필드가 갱신되므로, 토너먼트 데이터를 조회하는 다른 화면에서 최신 값을 보장하려면onSuccess에queryClient.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 valueNon-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사용 권장
JoinConfirmPayloadT와JoinWelcomePayloadT는 타입 선언으로만 사용되므로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빈 슬롯 렌더링 확인 필요.
isDepositClosed가true일 때 완전히 빈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
⛔ Files ignored due to path filters (4)
apps/web/src/app/home/_assets/sad-face.svgis excluded by!**/*.svgapps/web/src/app/tournament/[id]/create/_assets/confirm-exit-basket.svgis excluded by!**/*.svgapps/web/src/app/tournament/[id]/create/_assets/confirm-start-face.svgis excluded by!**/*.svgapps/web/src/assets/icons/fill/timer.svgis excluded by!**/*.svg
📒 Files selected for processing (58)
apps/web/src/apis/getNicknameCheck.tsapps/web/src/app/home/_components/InviteTournamentButton.tsxapps/web/src/app/home/_components/invite-code-dialog/InvalidCodeDialog.tsxapps/web/src/app/home/_components/invite-code-dialog/InviteCodeDialog.tsxapps/web/src/app/home/page.tsxapps/web/src/app/login/_components/LoginButtons.tsxapps/web/src/app/login/_components/SocialLoginButton.tsxapps/web/src/app/login/page.tsxapps/web/src/app/tournament/[id]/_common/_types/tournamentResponse.tsapps/web/src/app/tournament/[id]/create/_apis/getTournament.tsapps/web/src/app/tournament/[id]/create/_components/TournamentCreateClient.tsxapps/web/src/app/tournament/[id]/create/_components/deposit-countdown/DepositCountdown.tsxapps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriends.tsxapps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriendsDialog.tsxapps/web/src/app/tournament/[id]/create/_components/member-join-confirm-dialog/MemberJoinConfirmDialog.tsxapps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantChip.tsxapps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantPanel.tsxapps/web/src/app/tournament/[id]/create/_components/tournament-header/ConfirmExitDialog.tsxapps/web/src/app/tournament/[id]/create/_components/tournament-header/TournamentHeader.tsxapps/web/src/app/tournament/[id]/create/_components/tournament-item-basket-status/TournamentItemBasketStatus.tsxapps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/EmptyBasketSlot.tsxapps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasket.tsxapps/web/src/app/tournament/[id]/create/_components/tournament-item-basket/TournamentItemBasketCarousel.tsxapps/web/src/app/tournament/[id]/create/_components/tournament-start-button/ConfirmStartDialog.tsxapps/web/src/app/tournament/[id]/create/_components/tournament-start-button/TournamentStartButton.tsxapps/web/src/app/tournament/[id]/create/_components/welcome-join-dialog/WelcomeJoinDialog.tsxapps/web/src/app/tournament/[id]/create/_hooks/useCountdown.tsapps/web/src/app/tournament/[id]/create/_hooks/useGetTournamentItem.tsapps/web/src/app/tournament/[id]/create/_hooks/usePostTournamentItemLink.tsapps/web/src/app/tournament/[id]/create/_types/tournament.tsapps/web/src/app/tournament/[id]/create/by-wish/_components/WishSelectHeader.tsxapps/web/src/app/tournament/[id]/create/page.tsxapps/web/src/app/tournament/[id]/match/_hooks/useTournament.tsapps/web/src/app/tournament/[id]/match/page.tsxapps/web/src/app/tournament/[id]/result/_apis/postPlayLink.tsapps/web/src/app/tournament/[id]/result/_components/ReceiptDrawMachine.tsxapps/web/src/app/tournament/[id]/result/_components/ReceiptPaper.tsxapps/web/src/app/tournament/[id]/result/_components/ResultClient.tsxapps/web/src/app/tournament/[id]/result/_components/plate-share-dialog/PlateShareDialog.tsxapps/web/src/app/tournament/[id]/result/_hooks/usePostPlayLink.tsapps/web/src/app/tournament/join/[id]/_components/JoinPreviewClient.tsxapps/web/src/app/tournament/join/[id]/page.tsxapps/web/src/app/tournament/join/_apis/getInvitePreview.tsapps/web/src/app/tournament/join/_apis/getInvitePreviewByCode.tsapps/web/src/app/tournament/join/_apis/postJoinGuest.tsapps/web/src/app/tournament/join/_consts/randomNickname.tsapps/web/src/app/tournament/join/_hooks/useGetInvitePreview.tsapps/web/src/app/tournament/join/_hooks/usePostJoinGuest.tsapps/web/src/app/tournament/join/_types/join.tsapps/web/src/app/tournament/join/_utils/joinSession.tsapps/web/src/app/tournament/join/_utils/verifyInviteCode.tsapps/web/src/consts/api.tsapps/web/src/consts/route.tsapps/web/src/hooks/useGetNicknameCheck.tsapps/web/src/mocks/participants.tsapps/web/src/types/user.tsapps/web/src/utils/share.tsdocs/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
| import { getInvitePreviewByCode } from '../../../tournament/join/_apis/getInvitePreviewByCode'; | ||
| import { | ||
| CODE_LENGTH, | ||
| isValidInviteCodeFormat, | ||
| } from '../../../tournament/join/_utils/verifyInviteCode'; |
There was a problem hiding this comment.
상대 경로를 절대 경로로 변경하세요.
코딩 가이드라인에 따라 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.
| 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
| onError: error => { | ||
| // 400: 코드 없음 / 409: 만료 — 둘 다 사용자에게 InvalidCodeDialog 안내 | ||
| if (isAxiosError(error)) { | ||
| const status = error.response?.status; | ||
| if (status === 400 || status === 409) { | ||
| setIsInvalidDialogOpen(true); | ||
| return; | ||
| } | ||
| } | ||
| setIsInvalidDialogOpen(true); | ||
| }, |
There was a problem hiding this comment.
네트워크/서버 오류 시 더 적절한 에러 메시지를 제공하세요.
현재 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(); |
There was a problem hiding this comment.
typeof deadline === 'object' 검사 개선 필요
현재 코드는 deadline이 Date 객체일 때 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.
| 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.
| <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> |
There was a problem hiding this comment.
"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.
| * 링크 직접 접근 경로 (`piki.today/invite/{tournamentId}?code=XXX`). | ||
| */ | ||
| export const getInvitePreview = async (tournamentId: number, inviteCode?: string) => { | ||
| const params = inviteCode ? { inviteCode } : {}; | ||
|
|
There was a problem hiding this comment.
초대코드 쿼리 키 이름이 문서/주석과 불일치합니다.
주석은 ?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'; |
There was a problem hiding this comment.
상대 경로를 절대 경로로 변경하세요.
코딩 가이드라인에 따라 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.
| 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
| export const useGetInvitePreview = (tournamentId: number) => { | ||
| const { data: invitePreviewData } = useSuspenseQuery({ | ||
| queryKey: ['invitePreview', tournamentId], | ||
| queryFn: () => getInvitePreview(tournamentId), | ||
| }); |
There was a problem hiding this comment.
훅에서 inviteCode를 받지 않아 초대링크 검증 컨텍스트를 잃습니다.
getInvitePreview는 inviteCode를 지원하는데 이 훅은 tournamentId만 전달합니다. 결과적으로 코드별 검증/캐시 분리가 불가능합니다. 훅 시그니처와 queryKey를 inviteCode까지 포함하도록 확장해 주세요.
🤖 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.
| 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'; |
There was a problem hiding this comment.
🛠️ 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.
| 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
| 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), | ||
| }); |
There was a problem hiding this comment.
초대 링크의 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.
| ## 0. 공통 응답 래퍼 | ||
|
|
||
| 모든 응답은 `ApiResponseBody` 로 래핑됨. | ||
|
|
||
| ```json | ||
| { | ||
| "data": { | ||
| /* 실제 응답 */ | ||
| }, | ||
| "detail": "정상적으로 처리되었습니다.", | ||
| "pageResponse": { "nextCursor": null, "hasNext": false } | ||
| } | ||
| ``` | ||
|
|
||
| - 클라이언트는 axios 인터셉터에서 `.data.data` 를 풀어 사용 | ||
| - 실패 시 `data: null`, `detail`/`code` 에 사유 | ||
|
|
There was a problem hiding this comment.
문서의 공통 응답 계약을 실제 타입/가이드와 일치시켜 주세요.
현재 공통 응답 예시가 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
* 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
작업 요약
by-invite-code+invite-preview)join/guest)tournamentData.isOwner로 통일작업 세부 내용
API 연동
GET /users/nickname/check연동JoinPreviewClient닉네임 입력에 debounce(300ms) + 인라인 헬퍼/CTA 비활성GET /tournaments/by-invite-code연동 (백엔드 신규 endpoint)InviteCodeDialog의 mock 정답 코드(111111) 제거[A-Z]{3}\d{3}정규식 검증 (자동 대문자 변환, placeholderex. ABC123)InvalidCodeDialog노출GET /tournaments/{id}/invite-preview연동JoinPreviewClient의MOCK_TOURNAMENT_PREVIEW제거POST /tournaments/{id}/join/guest연동JoinPreviewClient의getTournamentList우회 라우팅 로직 제거POST /tournaments/{id}/play-link연동PlateShareDialog의 더미 URL 제거playLinkExpiresAt으로 만료 라벨 갱신 → Web Share API${origin}/play/{tournamentId}응답 타입 확장
tournamentData.isOwner(전 상태 공통) — 서버 boolean 필드로 주최자 분기tournamentData.pending.inviteCode,inviteExpiresAt— 친구 초대 동적 데이터tournamentData.completed.playLinkExpiresAt?,hasGroupResult— 결과 공유/그룹 결과 분기컴포넌트 정리
TournamentCreateClient—MOCK_DEPOSIT_DURATION_MS제거 →inviteExpiresAt기반 deadlineInviteFriendsDialog—tournamentData.pending.inviteExpiresAt으로 만료 시간 동적 표시,최대 7명 → 8명정책 일치ParticipantPanel—inviteCode기반 초대 URL 동적 구성 (${origin}/invite/{tournamentId}?code=XXX)PlateShareDialog— 서버 응답값 우선 표시, 14일 추정은 fallbackTournamentCreateClient—localStorage기반isParticipantOf제거 →!tournamentData.isOwnermock / dead code 제거
mocks/deposit.ts삭제mocks/tournamentPreview.ts삭제joinSession.ts의markAsParticipant/isParticipantOf/safeParseIds/PARTICIPANT_KEY삭제verifyInviteCode함수 → 형식 검증 함수(isValidInviteCodeFormat) 로 축소/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