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 (
-