diff --git a/frontend/src/components/Timer/index.tsx b/frontend/src/components/SocketTimer/index.tsx similarity index 65% rename from frontend/src/components/Timer/index.tsx rename to frontend/src/components/SocketTimer/index.tsx index ff15dfe..93a97d7 100644 --- a/frontend/src/components/Timer/index.tsx +++ b/frontend/src/components/SocketTimer/index.tsx @@ -1,21 +1,35 @@ import { css } from '@style/css'; +import { useEffect } from 'react'; + import Loading from '@/components/Common/Loading'; -import useTimer from '@/hooks/timer/useTimer'; +import useSocketTimer from '@/hooks/timer/useSocketTimer'; import { formatMilliSecond } from '@/utils/date'; import type { Socket } from '@/utils/socket'; interface Props { socket: Socket; + isConnected: boolean; endsAt: Date; - isConnected?: boolean; + onTimeout?: () => void; } -export default function Timer(props: Props) { - let { socket, endsAt, isConnected } = props; +export default function SocketTimer(props: Props) { + let { socket, endsAt, isConnected, onTimeout } = props; // api 연결이 X endsAt 대신 임시로 만들어놓은 것. - endsAt = new Date('2023-11-29T13:10:10.000Z'); - const { remainMiliSeconds } = useTimer({ socket, endsAt }); + // min 1 => 60초 동안 돌아갑니다. 변경해서 쓰세요 일단은.. + const min = 120; + endsAt = new Date(new Date().getTime() + min * 60 * 1000); + + const { remainMiliSeconds, isTimeout } = useSocketTimer({ + socket, + endsAt, + socketEvent: 'ping', + }); + + useEffect(() => { + if (isTimeout && typeof onTimeout === 'function') onTimeout(); + }, [isTimeout]); if (isConnected && remainMiliSeconds !== -1) { // 연결도 되어있고, 서버 시간도 도착해서 count down을 시작할 수 있을 때 diff --git a/frontend/src/components/Submission/Connecting.tsx b/frontend/src/components/Submission/Connecting.tsx index 3ba2fdc..5a314fe 100644 --- a/frontend/src/components/Submission/Connecting.tsx +++ b/frontend/src/components/Submission/Connecting.tsx @@ -7,7 +7,7 @@ interface Props { } export default function Connecting(props: Props) { - if (!props.isConnected) return null; + if (props.isConnected) return null; return (
@@ -16,6 +16,7 @@ export default function Connecting(props: Props) {
); } + const rowStyle = css({ display: 'flex', gap: '0.5rem', diff --git a/frontend/src/hooks/timer/useTimer.ts b/frontend/src/hooks/timer/useSocketTimer.ts similarity index 69% rename from frontend/src/hooks/timer/useTimer.ts rename to frontend/src/hooks/timer/useSocketTimer.ts index fba4d63..3d25bdc 100644 --- a/frontend/src/hooks/timer/useTimer.ts +++ b/frontend/src/hooks/timer/useSocketTimer.ts @@ -2,33 +2,36 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Socket } from '@/utils/socket'; -interface UseTimer { +interface Props { socket: Socket; endsAt: Date; - onTimeoutHandler?: () => void; + socketEvent: string; } -export default function useTimer({ socket, endsAt, onTimeoutHandler }: UseTimer) { +export default function useSocketTimer({ socket, endsAt, socketEvent }: Props) { const timerIntervalId = useRef(null); const endTime = useMemo(() => endsAt.getTime(), [endsAt]); + const [isTimeout, setIsTimeout] = useState(false); const [remainMiliSeconds, setRemainMiliSeconds] = useState(-1); + useEffect(() => { console.log('타이머 실행'); // 웹 소켓 대신 사용. mockWebSocket(); - if (!socket.hasListeners('ping')) { - socket.on('ping', handlePingMessage); + socket.emit(socketEvent); + if (socket.hasListeners(socketEvent)) { + socket.on(socketEvent, handlePingMessage); } }, [socket]); const handlePingMessage = useCallback((time: Date | string) => { + console.log(time); if (timerIntervalId.current) clearInterval(timerIntervalId.current); time = typeof time === 'string' ? new Date(time) : time; const remainMiliSec = endTime - time.getTime(); setRemainMiliSeconds(remainMiliSec); timerIntervalId.current = setInterval(() => { - console.log('1초마다 실행'); setRemainMiliSeconds((prev) => prev - 1000); }, 1000); }, []); @@ -37,20 +40,20 @@ export default function useTimer({ socket, endsAt, onTimeoutHandler }: UseTimer) // 웹 소켓 연결 후 삭제 예정 const mockWebSocket = useCallback(() => { const delayFactor = 2000; + const serverTime = new Date(); + handlePingMessage(serverTime); setInterval(() => { - console.log('ping 5초( + 네트워크 지연) 마다 실행'); const serverTime = new Date(); handlePingMessage(serverTime); }, 5000 + Math.random() * delayFactor); }, []); useEffect(() => { - // TODO time 0이면 대시보드로 이동하는 로직 - // 해당 PR에서 해결할 문제는 아니라 PASS + // 초기 값인 -1 => 서버에서 시간이 오지 않았다. + if (remainMiliSeconds === -1) return; if (Math.floor(remainMiliSeconds / 1000) <= 0) { - if (typeof onTimeoutHandler === 'function') onTimeoutHandler(); - // 나가는 로직 + setIsTimeout(true); } }, [remainMiliSeconds]); - return { remainMiliSeconds }; + return { remainMiliSeconds, isTimeout }; } diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index fe55a8f..2bf1fa0 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -2,6 +2,7 @@ import { css } from '@style/css'; import { useContext, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { ModalContext } from '@/components/Common/Modal/ModalContext'; import CompetitionHeader from '@/components/Contest/CompetitionHeader'; @@ -10,8 +11,8 @@ import Editor from '@/components/Editor/Editor'; import ProblemViewer from '@/components/Problem/ProblemViewer'; import { SimulationInputModal } from '@/components/Simulation/SimulationInputModal'; import { SimulationResultList } from '@/components/Simulation/SimulationResultList'; +import SocketTimer from '@/components/SocketTimer'; import { SubmissionResult } from '@/components/Submission'; -import Timer from '@/components/Timer'; import { SITE } from '@/constants'; import type { SubmissionForm } from '@/hooks/competition'; import { useCompetition } from '@/hooks/competition'; @@ -22,6 +23,7 @@ import { isNil } from '@/utils/type'; const RUN_SIMULATION = '테스트 실행'; const CANCEL_SIMULATION = '실행 취소'; +const DASHBOARD_URL = '/contest/dashboard'; export default function ContestPage() { const { id } = useParams<{ id: string }>(); @@ -29,6 +31,8 @@ export default function ContestPage() { const [currentProblemIndex, setCurrentProblemIndex] = useState(0); const modal = useContext(ModalContext); + const navigate = useNavigate(); + const simulation = useSimulation(); const { socket, competition, submitSolution, isConnected } = useCompetition(competitionId); @@ -82,22 +86,29 @@ export default function ContestPage() { submitSolution(form); } - const { endsAt } = competition; function handleOpenModal() { modal.open(); } - + + function handleTimeout() { + navigate(`${DASHBOARD_URL}/${competitionId}`); + } + const problems = problemList.map((problem) => problem.id); - return (
-
+
{problem.title} - +