From 9abf00752e7f9f94eb9daa18f97117b416b0a8cc Mon Sep 17 00:00:00 2001 From: joyeongchan Date: Fri, 24 Jul 2026 23:29:08 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=EC=9D=B4=EB=AF=B8=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=ED=95=9C=20=EA=B2=8C=EC=8A=A4=ED=8A=B8=EC=9D=98=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EB=A7=81=ED=81=AC=20=EC=9E=AC=EC=A7=84?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=ED=86=A0=EB=84=88=EB=A8=BC=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EB=A1=9C=20=EC=A7=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../invite/[id]/_components/InviteClient.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/web/src/app/invite/[id]/_components/InviteClient.tsx b/apps/web/src/app/invite/[id]/_components/InviteClient.tsx index d889750d..3492aa3d 100644 --- a/apps/web/src/app/invite/[id]/_components/InviteClient.tsx +++ b/apps/web/src/app/invite/[id]/_components/InviteClient.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation'; import { useEffect, useRef, useState } from 'react'; import { getMe } from '@/apis/getMe'; +import { getTournament } from '@/app/tournament/[id]/_common/_apis/getTournament'; import { getInvitePreviewByCode } from '@/app/tournament/join/_apis/getInvitePreviewByCode'; import { postJoin } from '@/app/tournament/join/_apis/postJoin'; import Button from '@/components/button'; @@ -75,6 +76,24 @@ function InviteClient({ tournamentId, inviteCode }: InviteClientProps) { return; } + /** + * 이미 참여한 게스트가 같은 링크로 재진입한 경우 join 플로우를 건너뛰고 토너먼트로 바로 진입. + * 참여 여부는 상세 조회(getTournament)의 200/403 으로 판별한다 — 참여자면 200, 미참여면 403. + * (409 는 "만료·이미 시작" 등과 뒤섞여 있어 참여 판별에 쓰기 부적절) + */ + const isAlreadyJoined = await getTournament(tournamentId) + .then(() => true) + .catch(error => { + if (isAxiosError(error) && error.response?.status === 403) + return false; + throw error; + }); + + if (isAlreadyJoined) { + router.replace(ROUTES.TOURNAMENT_CREATE(tournamentId)); + return; + } + router.replace(`${ROUTES.TOURNAMENT_JOIN_BY_LINK(tournamentId)}?code=${inviteCode}`); } catch (error) { if (isAxiosError(error) && error.response?.status === 409) { From 6de6c3407db6ee1e03207211f234c7add0d216ba Mon Sep 17 00:00:00 2001 From: joyeongchan Date: Sun, 26 Jul 2026 20:06:20 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=EA=B2=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=AC=EC=A7=84=EC=9E=85=20=ED=8C=90=EB=B3=84=EC=9D=84=20get?= =?UTF-8?q?Tournament=20=EB=8C=80=EC=8B=A0=20preview=20joined=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/invite/[id]/_components/InviteClient.tsx | 16 +++------------- apps/web/src/app/tournament/join/_types/join.ts | 2 ++ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/web/src/app/invite/[id]/_components/InviteClient.tsx b/apps/web/src/app/invite/[id]/_components/InviteClient.tsx index 3492aa3d..76f60b42 100644 --- a/apps/web/src/app/invite/[id]/_components/InviteClient.tsx +++ b/apps/web/src/app/invite/[id]/_components/InviteClient.tsx @@ -6,7 +6,6 @@ import { useRouter } from 'next/navigation'; import { useEffect, useRef, useState } from 'react'; import { getMe } from '@/apis/getMe'; -import { getTournament } from '@/app/tournament/[id]/_common/_apis/getTournament'; import { getInvitePreviewByCode } from '@/app/tournament/join/_apis/getInvitePreviewByCode'; import { postJoin } from '@/app/tournament/join/_apis/postJoin'; import Button from '@/components/button'; @@ -77,19 +76,10 @@ function InviteClient({ tournamentId, inviteCode }: InviteClientProps) { } /** - * 이미 참여한 게스트가 같은 링크로 재진입한 경우 join 플로우를 건너뛰고 토너먼트로 바로 진입. - * 참여 여부는 상세 조회(getTournament)의 200/403 으로 판별한다 — 참여자면 200, 미참여면 403. - * (409 는 "만료·이미 시작" 등과 뒤섞여 있어 참여 판별에 쓰기 부적절) + * 이미 참여한 게스트가 같은 링크로 재진입하면 join 플로우를 건너뛰고 토너먼트로 바로 진입. + * preview 응답의 joined 로 판별한다 — 별도 조회 없이 첫 preview 한 번으로 끝난다. */ - const isAlreadyJoined = await getTournament(tournamentId) - .then(() => true) - .catch(error => { - if (isAxiosError(error) && error.response?.status === 403) - return false; - throw error; - }); - - if (isAlreadyJoined) { + if (preview.joined) { router.replace(ROUTES.TOURNAMENT_CREATE(tournamentId)); return; } diff --git a/apps/web/src/app/tournament/join/_types/join.ts b/apps/web/src/app/tournament/join/_types/join.ts index 1673c672..b32ebe75 100644 --- a/apps/web/src/app/tournament/join/_types/join.ts +++ b/apps/web/src/app/tournament/join/_types/join.ts @@ -4,6 +4,8 @@ export type GetInvitePreviewResponseT = { tournamentName: string; itemCount: number; participantCount: number; + /** 요청자(게스트/회원)가 이미 이 토너먼트에 참여 중인지. 미인증·미참여면 false */ + joined: boolean; }; export type PostJoinRequestT = { From b26beea408a69d892297eff2c91a11ec0dd03136 Mon Sep 17 00:00:00 2001 From: joyeongchan Date: Tue, 28 Jul 2026 00:13:36 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EB=A9=A4=EB=B2=84=EB=8F=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=20=EC=B0=B8=EC=97=AC=ED=95=9C=20=ED=86=A0?= =?UTF-8?q?=EB=84=88=EB=A8=BC=ED=8A=B8=EB=A9=B4=20join=20=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EA=B1=B4=EB=84=88=EB=9B=B0=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20joined=20=ED=8C=90=EB=B3=84=EC=9D=84=20=EC=95=9E?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/invite/[id]/_components/InviteClient.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/invite/[id]/_components/InviteClient.tsx b/apps/web/src/app/invite/[id]/_components/InviteClient.tsx index 76f60b42..b9be1e69 100644 --- a/apps/web/src/app/invite/[id]/_components/InviteClient.tsx +++ b/apps/web/src/app/invite/[id]/_components/InviteClient.tsx @@ -69,21 +69,21 @@ function InviteClient({ tournamentId, inviteCode }: InviteClientProps) { return; } - const user = await getMe().catch(() => null); - if (user?.identityType === 'MEMBER') { - await joinAsMemberAndGoToCreate(); - return; - } - /** - * 이미 참여한 게스트가 같은 링크로 재진입하면 join 플로우를 건너뛰고 토너먼트로 바로 진입. - * preview 응답의 joined 로 판별한다 — 별도 조회 없이 첫 preview 한 번으로 끝난다. + * 이미 참여한 유저(멤버·게스트 공통)가 같은 링크로 재진입하면 join 플로우를 건너뛰고 + * 토너먼트로 바로 진입. preview 응답의 joined 로 판별 — 별도 조회 없이 preview 한 번으로 끝난다. */ if (preview.joined) { router.replace(ROUTES.TOURNAMENT_CREATE(tournamentId)); return; } + const user = await getMe().catch(() => null); + if (user?.identityType === 'MEMBER') { + await joinAsMemberAndGoToCreate(); + return; + } + router.replace(`${ROUTES.TOURNAMENT_JOIN_BY_LINK(tournamentId)}?code=${inviteCode}`); } catch (error) { if (isAxiosError(error) && error.response?.status === 409) {