-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 소셜 토너먼트 전체 (4개 PR 통합 — #142/#156/#159/#163) #167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
a52d326
Squashed commit of the following:
ychany dd3fe59
fix: 코드 검증 중 입력 변경 시 race condition 방지
ychany 70d173f
fix: PlayClient 의 goToTournament 호출에 await 추가 (무한 로딩 방지)
ychany ffa43e3
feat: 게스트도 친구 토너먼트 결과보기 (sourceTournamentId 활용)
ychany 2f1a307
refactor: InviteClient 의 의미 없는 Axios 에러 분기 제거
ychany 1f337df
refactor: play _apis/_types 를 [id] 라우트 내부로 이동
ychany 3c6a7b7
refactor: 토너먼트 응답 타입/훅 중복 제거 및 _common 으로 통합
ychany a81ee23
fix: 토너먼트 prefetch queryKey 의 id 타입 number 로 통일
ychany 60c7a78
refactor: create 자식 컴포넌트의 중복 useGetTournament 호출 제거
ychany f6852c1
refactor: result 페이지 영수증 포맷 유틸을 _utils 로 추출
ychany 22e94ce
chore: TournamentItemBasket 의 사용 변수에서 _ prefix 제거
ychany e6fdea6
refactor: JoinPreviewClient 의 중복 null 체크 제거 및 tournamentId 타입 좁히기
ychany 90abb95
Merge remote-tracking branch 'origin/dev' into feat/social-tournament…
ychany e8f60c9
Merge remote-tracking branch 'origin/dev' into feat/social-tournament…
ychany 7c61c41
chore: format
ychany File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { environmentManager } from '@tanstack/react-query'; | ||
|
|
||
| import { ENDPOINTS } from '@/consts/api'; | ||
| import type { ApiResponseT } from '@/types/api'; | ||
| import type { GetNicknameCheckResponseT } from '@/types/user'; | ||
|
|
||
| import { clientApi } from './client'; | ||
| import { serverApi } from './server'; | ||
|
|
||
| /** | ||
| * 닉네임 중복 체크. | ||
| * 본인의 현재 닉네임은 서버가 자동으로 통과시킨다 (재확인 흐름 호환). | ||
| */ | ||
| export const getNicknameCheck = async (nickname: string) => { | ||
| if (environmentManager.isServer()) { | ||
| const { data } = await serverApi.get<ApiResponseT<GetNicknameCheckResponseT>>( | ||
| ENDPOINTS.USERS_NICKNAME_CHECK, | ||
| { params: { nickname } } | ||
| ); | ||
| return data.data; | ||
| } | ||
|
|
||
| const { data } = await clientApi.get<ApiResponseT<GetNicknameCheckResponseT>>( | ||
| ENDPOINTS.USERS_NICKNAME_CHECK, | ||
| { params: { nickname } } | ||
| ); | ||
| return data.data; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
apps/web/src/app/auth/callback/[provider]/_apis/postSocialLogin.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions
30
apps/web/src/app/home/_components/InviteTournamentButton.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| 'use client'; | ||
|
|
||
| import { useState } from 'react'; | ||
|
|
||
| import { LoginIconOutline } from '@/assets/icons'; | ||
|
|
||
| import InviteCodeDialog from './invite-code-dialog/InviteCodeDialog'; | ||
|
|
||
| function InviteTournamentButton() { | ||
| const [isOpen, setIsOpen] = useState(false); | ||
|
|
||
| const handleOpen = () => setIsOpen(true); | ||
|
|
||
| return ( | ||
| <> | ||
| <button | ||
| type="button" | ||
| onClick={handleOpen} | ||
| className="flex h-[54px] w-full cursor-pointer items-center justify-center gap-2 rounded-[12px] bg-bg-layer-default px-9" | ||
| > | ||
| <LoginIconOutline className="size-6 text-icon-neutral-secondary" /> | ||
| <span className="body-1-semibold text-text-neutral-primary">초대 토너먼트 입장</span> | ||
| </button> | ||
|
|
||
| <InviteCodeDialog open={isOpen} onOpenChange={setIsOpen} /> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default InviteTournamentButton; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
apps/web/src/app/home/_components/invite-code-dialog/InvalidCodeDialog.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| 'use client'; | ||
|
|
||
| import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/components/dialog'; | ||
|
|
||
| import SadFace from '../../_assets/sad-face.svg'; | ||
|
|
||
| type InvalidCodeDialogProps = { | ||
| open: boolean; | ||
| onOpenChange: (open: boolean) => void; | ||
| }; | ||
|
|
||
| function InvalidCodeDialog({ open, onOpenChange }: InvalidCodeDialogProps) { | ||
| const handleClose = () => onOpenChange(false); | ||
|
|
||
| return ( | ||
| <Dialog open={open} onOpenChange={onOpenChange}> | ||
| <DialogContent showCloseButton={false} className="flex flex-col items-center gap-5 p-6"> | ||
| <SadFace className="size-7.75" aria-hidden /> | ||
|
|
||
| <div className="flex flex-col items-center gap-1"> | ||
| <DialogTitle className="text-center heading-1 text-text-neutral-primary"> | ||
| 코드가 유효하지 않아요 | ||
| </DialogTitle> | ||
| <DialogDescription className="text-center body-2-medium text-text-neutral-tertiary"> | ||
| 입력한 코드와 일치하는 토너먼트가 없어요. | ||
| <br /> | ||
| 코드를 다시 확인해주세요. | ||
| </DialogDescription> | ||
| </div> | ||
|
|
||
| <button | ||
| type="button" | ||
| onClick={handleClose} | ||
| className="flex h-13 w-full cursor-pointer items-center justify-center rounded-xl bg-bg-neutral-primary body-1-semibold text-text-neutral-inverse" | ||
| > | ||
| 닫기 | ||
| </button> | ||
| </DialogContent> | ||
| </Dialog> | ||
| ); | ||
| } | ||
|
|
||
| export default InvalidCodeDialog; |
118 changes: 118 additions & 0 deletions
118
apps/web/src/app/home/_components/invite-code-dialog/InviteCodeDialog.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| 'use client'; | ||
|
|
||
| import { useMutation } from '@tanstack/react-query'; | ||
| import { isAxiosError } from 'axios'; | ||
| import { useRouter } from 'next/navigation'; | ||
| import { useState } from 'react'; | ||
|
|
||
| import { getInvitePreviewByCode } from '@/app/tournament/join/_apis/getInvitePreviewByCode'; | ||
| import { | ||
| CODE_LENGTH, | ||
| isValidInviteCodeFormat, | ||
| } from '@/app/tournament/join/_utils/verifyInviteCode'; | ||
| import Button from '@/components/button'; | ||
| import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/components/dialog'; | ||
| import Input from '@/components/input'; | ||
| import Spinner from '@/components/spinner'; | ||
|
|
||
| import InvalidCodeDialog from './InvalidCodeDialog'; | ||
|
|
||
| type InviteCodeDialogProps = { | ||
| open: boolean; | ||
| onOpenChange: (open: boolean) => void; | ||
| }; | ||
|
|
||
| function InviteCodeDialog({ open, onOpenChange }: InviteCodeDialogProps) { | ||
| const router = useRouter(); | ||
| const [code, setCode] = useState(''); | ||
| const [showFormatError, setShowFormatError] = useState(false); | ||
| const [isInvalidDialogOpen, setIsInvalidDialogOpen] = useState(false); | ||
|
|
||
| const { mutate: previewMutation, isPending: isPreviewPending } = useMutation({ | ||
| mutationFn: getInvitePreviewByCode, | ||
| // mutation 진행 중 사용자가 input 을 바꿀 수 있으므로 | ||
| // 검증에 사용한 변수 (variables) 를 그대로 라우팅에 쓴다 — state 의 code 를 다시 읽지 않는다. | ||
| onSuccess: (data, enteredCode) => { | ||
| onOpenChange(false); | ||
| reset(); | ||
| router.push(`/tournament/join/${data.tournamentId}?code=${enteredCode}`); | ||
| }, | ||
| onError: error => { | ||
| // 400: 코드 없음 / 409: 만료 — 둘 다 사용자에게 InvalidCodeDialog 안내 | ||
| if (isAxiosError(error)) { | ||
| const status = error.response?.status; | ||
| if (status === 400 || status === 409) { | ||
| setIsInvalidDialogOpen(true); | ||
| return; | ||
| } | ||
| } | ||
| setIsInvalidDialogOpen(true); | ||
| }, | ||
| }); | ||
|
|
||
| const isComplete = code.length === CODE_LENGTH; | ||
| const canSubmit = isComplete && !isPreviewPending; | ||
|
|
||
| const reset = () => { | ||
| setCode(''); | ||
| setShowFormatError(false); | ||
| }; | ||
|
|
||
| const handleOpenChange = (next: boolean) => { | ||
| if (!next) reset(); | ||
| onOpenChange(next); | ||
| }; | ||
|
|
||
| const handleChange = (next: string) => { | ||
| setCode(next.slice(0, CODE_LENGTH).toUpperCase()); | ||
| if (showFormatError) setShowFormatError(false); | ||
| }; | ||
|
|
||
| const handleSubmit = () => { | ||
| if (!canSubmit) return; | ||
|
|
||
| if (!isValidInviteCodeFormat(code)) { | ||
| setShowFormatError(true); | ||
| return; | ||
| } | ||
|
|
||
| previewMutation(code); | ||
| }; | ||
|
|
||
| return ( | ||
| <> | ||
| <Dialog open={open} onOpenChange={handleOpenChange}> | ||
| <DialogContent showCloseButton={false} className="flex flex-col gap-5 p-6"> | ||
| <DialogTitle className="text-center heading-1 text-text-neutral-primary"> | ||
| 초대받은 토너먼트 | ||
| </DialogTitle> | ||
| <DialogDescription className="sr-only">초대 코드를 입력해 입장합니다.</DialogDescription> | ||
|
|
||
| <Input | ||
| label="초대 코드" | ||
| placeholder="ex. ABC123" | ||
| value={code} | ||
| onChange={event => handleChange(event.target.value)} | ||
| aria-invalid={showFormatError} | ||
| {...(showFormatError | ||
| ? { helperText: '영문 대문자 3자 + 숫자 3자로 입력해주세요.' } | ||
| : {})} | ||
| maxLength={CODE_LENGTH} | ||
| autoCapitalize="characters" | ||
| autoCorrect="off" | ||
| spellCheck={false} | ||
| autoFocus | ||
| /> | ||
|
|
||
| <Button size="lg" variant="primary" disabled={!canSubmit} onClick={handleSubmit}> | ||
| {isPreviewPending ? <Spinner size={20} /> : '입장하기'} | ||
| </Button> | ||
| </DialogContent> | ||
| </Dialog> | ||
|
|
||
| <InvalidCodeDialog open={isInvalidDialogOpen} onOpenChange={setIsInvalidDialogOpen} /> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default InviteCodeDialog; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.