Skip to content

Commit

Permalink
Merge pull request #150 from boostcampwm2023/131-대회-종료시-대시보드로-이동-기능
Browse files Browse the repository at this point in the history
[#131] 대회 종료시 대시보드로 이동 기능
  • Loading branch information
mahwin authored Nov 29, 2023
2 parents 2ffee35 + 6294ffc commit 51ba82e
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -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을 시작할 수 있을 때
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/Submission/Connecting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface Props {
}

export default function Connecting(props: Props) {
if (!props.isConnected) return null;
if (props.isConnected) return null;

return (
<div className={rowStyle}>
Expand All @@ -16,6 +16,7 @@ export default function Connecting(props: Props) {
</div>
);
}

const rowStyle = css({
display: 'flex',
gap: '0.5rem',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<NodeJS.Timeout | null>(null);
const endTime = useMemo(() => endsAt.getTime(), [endsAt]);
const [isTimeout, setIsTimeout] = useState(false);
const [remainMiliSeconds, setRemainMiliSeconds] = useState<number>(-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);
}, []);
Expand All @@ -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 };
}
23 changes: 17 additions & 6 deletions frontend/src/pages/ContestPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -22,13 +23,16 @@ 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 }>();
const competitionId: number = id ? parseInt(id, 10) : -1;
const [currentProblemIndex, setCurrentProblemIndex] = useState(0);
const modal = useContext(ModalContext);

const navigate = useNavigate();

const simulation = useSimulation();

const { socket, competition, submitSolution, isConnected } = useCompetition(competitionId);
Expand Down Expand Up @@ -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 (
<main className={style}>
<CompetitionHeader crumbs={crumbs} id={competitionId} />
<section>
<section className={rowStyle}>
<span className={problemTitleStyle}>{problem.title}</span>
<Timer socket={socket.current} isConnected={isConnected} endsAt={new Date(endsAt)} />
<SocketTimer
socket={socket.current}
isConnected={isConnected}
endsAt={new Date(endsAt)}
onTimeout={handleTimeout}
/>
</section>
<section className={rowListStyle}>
<ContestProblemSelector
Expand Down

0 comments on commit 51ba82e

Please sign in to comment.