-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FE] - Session, Quiz-wait 페이지 이벤트 로직 변경 (#155)
* feat: pinCode Check API 구현 * feat: Nickname 페이지 커스텀 훅 구현 * feat: 메인 페이지에서 핀코드 유효성(방 정원 초과 여부) 검사 * fix: session 이벤트 수정 * fix: 커스텀 훅 적용 및 lazy 페이지로 변경 * fix: import 경로 변경 * fix: Prevent Router 적용
- Loading branch information
1 parent
1481bff
commit 19d4ee4
Showing
9 changed files
with
185 additions
and
149 deletions.
There are no files selected for viewing
This file contains 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 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 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 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,122 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { useNavigate, useParams } from 'react-router-dom'; | ||
import { Copy, Users, PlayCircle } from 'lucide-react'; | ||
|
||
import { getQuizSocket } from '@/shared/utils/socket'; | ||
import { getCookie } from '@/shared/utils/cookie'; | ||
import { toastController } from '@/features/toast/model/toastController'; | ||
import { apiClient } from '@/shared/api'; | ||
import UserGridView from './ui/UserGridView'; | ||
import { useNickname } from './model/hooks/useNickname'; | ||
|
||
export interface Guest { | ||
nickname: string; | ||
character: number; | ||
position: number; | ||
} | ||
|
||
export default function QuizWaitLazyPage() { | ||
const socket = getQuizSocket(); | ||
const { pinCode } = useParams(); | ||
const { | ||
data: { participantList, myPosition }, | ||
refetch, | ||
} = useNickname(socket, pinCode ?? '', getCookie('sid') ?? ''); | ||
const [userType, setUserType] = useState<string>(''); | ||
|
||
const guestLink = `${import.meta.env.VITE_CLIENT_URL}/nickname/${pinCode}`; | ||
const toast = toastController(); | ||
|
||
const navigate = useNavigate(); | ||
|
||
useEffect(() => { | ||
const handleParticipantNotice = () => { | ||
refetch(); | ||
}; | ||
socket.on('participant notice', handleParticipantNotice); | ||
|
||
const fetchUserType = async () => { | ||
const response = await apiClient.get(`/games/${pinCode}/sid/${getCookie('sid')}`); | ||
setUserType(response.type); | ||
}; | ||
|
||
socket.on('start quiz', (response) => { | ||
console.log('start quiz', response); | ||
navigate(`/quiz/session/${pinCode}/1`); | ||
}); | ||
|
||
fetchUserType(); | ||
|
||
return () => { | ||
socket.off('participant notice', handleParticipantNotice); | ||
}; | ||
}, []); | ||
|
||
const handleCopyLink = () => { | ||
try { | ||
navigator.clipboard.writeText(guestLink); | ||
toast.success('링크가 복사되었습니다.'); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
const errorMessage = error.message; | ||
toast.error(errorMessage); | ||
} | ||
} | ||
}; | ||
|
||
const handleQuizStart = () => { | ||
socket.emit('start quiz', { sid: getCookie('sid'), pinCode }); | ||
navigate(`/quiz/session/host/${pinCode}/1`); | ||
}; | ||
|
||
return ( | ||
<div className="flex justify-center gap-6 pt-8"> | ||
<div className="flex flex-col gap-6 justify-center items-center"> | ||
<div className="w-full bg-white rounded-xl shadow-md p-6"> | ||
<div className="relative flex items-center justify-between mb-4 gap-2"> | ||
<div className="flex items-center gap-2"> | ||
<div className="text-lg font-bold px-4 py-2 rounded-xl bg-gray-100"> | ||
<span className="text-gray-500">Pin : </span> | ||
{pinCode} | ||
</div> | ||
<div | ||
className="px-4 py-2 bg-blue-500 text-white rounded-xl hover:bg-blue-600 flex items-center gap-2 cursor-pointer" | ||
onClick={handleCopyLink} | ||
> | ||
<Copy className="w-4 h-4" /> | ||
링크 복사하기 | ||
</div> | ||
</div> | ||
|
||
<p className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-fit flex items-center gap-2 justify-center font-medium text-md text-gray-700"> | ||
<span className="text-blue-500 font-bold"> / </span>입력 시 채팅을 칠 수 있어요. | ||
</p> | ||
|
||
<p className="flex items-center gap-6 font-bold text-lg"> | ||
<div className="flex items-center gap-3 bg-gray-100 px-4 py-2 rounded-2xl"> | ||
<Users className="w-5 h-5 text-gray-600" /> | ||
<span className="font-medium text-gray-700">{participantList.length}명</span> | ||
</div> | ||
<div className="flex items-center gap-2"> | ||
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse" /> | ||
<span className="text-gray-500 font-medium">참가 대기중</span> | ||
</div> | ||
</p> | ||
</div> | ||
<UserGridView guests={participantList} myPosition={myPosition} /> | ||
</div> | ||
{userType === 'master' && ( | ||
<div className="flex justify-end min-w-full"> | ||
<button | ||
className="px-4 py-2 bg-blue-500 text-white rounded-xl hover:bg-blue-600 flex items-center gap-2 cursor-pointer" | ||
onClick={handleQuizStart} | ||
> | ||
<PlayCircle className="w-6 h-6 text-white" /> | ||
퀴즈 시작하기 | ||
</button> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains 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 |
---|---|---|
@@ -1,129 +1,10 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { useNavigate, useParams } from 'react-router-dom'; | ||
import { Copy, Users, PlayCircle } from 'lucide-react'; | ||
|
||
import { getQuizSocket } from '@/shared/utils/socket'; | ||
import { getCookie } from '@/shared/utils/cookie'; | ||
import { toastController } from '@/features/toast/model/toastController'; | ||
import { apiClient } from '@/shared/api'; | ||
import UserGridView from './ui/UserGridView'; | ||
|
||
export interface Guest { | ||
nickname: string; | ||
character: number; | ||
position: number; | ||
} | ||
|
||
export default function QuizWait() { | ||
const { pinCode } = useParams(); | ||
const [userType, setUserType] = useState<string>(''); | ||
const [guests, setGuests] = useState<Guest[]>([]); | ||
const [myPosition, setMyPosition] = useState<number>(-1); | ||
|
||
const guestLink = `${import.meta.env.VITE_CLIENT_URL}/nickname/${pinCode}`; | ||
const socket = getQuizSocket(); | ||
const toast = toastController(); | ||
|
||
const navigate = useNavigate(); | ||
|
||
useEffect(() => { | ||
const handleMyPosition = (response: any) => { | ||
const { participantList, myPosition } = response; | ||
setGuests(participantList); | ||
setMyPosition(myPosition); | ||
}; | ||
|
||
socket.on('my position', handleMyPosition); | ||
|
||
const handleNickname = (response: any) => { | ||
const { participantList } = response; | ||
setGuests(participantList); | ||
}; | ||
|
||
socket.on('nickname', handleNickname); | ||
|
||
const fetchUserType = async () => { | ||
const response = await apiClient.get(`/games/${pinCode}/sid/${getCookie('sid')}`); | ||
setUserType(response.type); | ||
}; | ||
|
||
socket.on('start quiz', (response) => { | ||
console.log('start quiz', response); | ||
navigate(`/quiz/session/${pinCode}/1`); | ||
}); | ||
|
||
fetchUserType(); | ||
|
||
return () => { | ||
socket.off('nickname', handleNickname); | ||
}; | ||
}, []); | ||
|
||
const handleCopyLink = () => { | ||
try { | ||
navigator.clipboard.writeText(guestLink); | ||
toast.success('링크가 복사되었습니다.'); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
const errorMessage = error.message; | ||
toast.error(errorMessage); | ||
} | ||
} | ||
}; | ||
|
||
const handleQuizStart = () => { | ||
socket.emit('start quiz', { sid: getCookie('sid'), pinCode }); | ||
navigate(`/quiz/session/host/${pinCode}/1`); | ||
}; | ||
import AsyncBoundary from '@/shared/boundary/AsyncBoundary'; | ||
import QuizWaitLazyPage from './index.lazy'; | ||
|
||
export default function QuizWaitPage() { | ||
return ( | ||
<div className="flex justify-center gap-6 pt-8"> | ||
<div className="flex flex-col gap-6 justify-center items-center"> | ||
<div className="w-full bg-white rounded-xl shadow-md p-6"> | ||
<div className="relative flex items-center justify-between mb-4 gap-2"> | ||
<div className="flex items-center gap-2"> | ||
<div className="text-lg font-bold px-4 py-2 rounded-xl bg-gray-100"> | ||
<span className="text-gray-500">Pin : </span> | ||
{pinCode} | ||
</div> | ||
<div | ||
className="px-4 py-2 bg-blue-500 text-white rounded-xl hover:bg-blue-600 flex items-center gap-2 cursor-pointer" | ||
onClick={handleCopyLink} | ||
> | ||
<Copy className="w-4 h-4" /> | ||
링크 복사하기 | ||
</div> | ||
</div> | ||
|
||
<p className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-fit flex items-center gap-2 justify-center font-medium text-md text-gray-700"> | ||
<span className="text-blue-500 font-bold"> / </span>입력 시 채팅을 칠 수 있어요. | ||
</p> | ||
|
||
<p className="flex items-center gap-6 font-bold text-lg"> | ||
<div className="flex items-center gap-3 bg-gray-100 px-4 py-2 rounded-2xl"> | ||
<Users className="w-5 h-5 text-gray-600" /> | ||
<span className="font-medium text-gray-700">{guests.length}명</span> | ||
</div> | ||
<div className="flex items-center gap-2"> | ||
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse" /> | ||
<span className="text-gray-500 font-medium">참가 대기중</span> | ||
</div> | ||
</p> | ||
</div> | ||
<UserGridView guests={guests} myPosition={myPosition} /> | ||
</div> | ||
{userType === 'master' && ( | ||
<div className="flex justify-end min-w-full"> | ||
<button | ||
className="px-4 py-2 bg-blue-500 text-white rounded-xl hover:bg-blue-600 flex items-center gap-2 cursor-pointer" | ||
onClick={handleQuizStart} | ||
> | ||
<PlayCircle className="w-6 h-6 text-white" /> | ||
퀴즈 시작하기 | ||
</button> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
<AsyncBoundary> | ||
<QuizWaitLazyPage /> | ||
</AsyncBoundary> | ||
); | ||
} |
17 changes: 17 additions & 0 deletions
17
packages/client/src/pages/quiz-wait/model/hooks/useNickname.ts
This file contains 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,17 @@ | ||
import { useSuspenseQuery } from '@tanstack/react-query'; | ||
|
||
import { emitEventWithAck } from '@/shared/utils/emitEventWithAck'; | ||
import { Socket } from 'socket.io-client'; | ||
import { Guest } from '../../index.lazy'; | ||
|
||
interface NicknameResponse { | ||
participantList: Guest[]; | ||
myPosition: number; | ||
} | ||
|
||
export const useNickname = (socket: Socket, pinCode: string, sid: string) => { | ||
return useSuspenseQuery({ | ||
queryKey: ['participant info', pinCode], | ||
queryFn: () => emitEventWithAck<NicknameResponse>(socket, 'participant info', { pinCode, sid }), | ||
}); | ||
}; |
This file contains 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
Oops, something went wrong.