From bb31630db0b492c97d9c41514db43a8b17706d73 Mon Sep 17 00:00:00 2001 From: anttiey Date: Tue, 14 Jan 2025 16:08:05 +0900 Subject: [PATCH 1/4] =?UTF-8?q?:recycle:=20=EA=B8=B0=EC=A1=B4=20=ED=83=80?= =?UTF-8?q?=EC=9D=B4=EB=A8=B8=20=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 4 +- frontend/src/apis/{ => http}/category.ts | 2 +- frontend/src/apis/{ => http}/fetcher.ts | 0 frontend/src/apis/{ => http}/github.ts | 0 frontend/src/apis/{ => http}/member.ts | 4 +- frontend/src/apis/{ => http}/oauth.ts | 2 +- frontend/src/apis/{ => http}/pairRoom.ts | 6 +- frontend/src/apis/{ => http}/referenceLink.ts | 2 +- frontend/src/apis/{ => http}/retrospect.ts | 2 +- frontend/src/apis/{ => http}/timer.ts | 7 +- frontend/src/apis/{ => http}/todo.ts | 2 +- frontend/src/apis/websocket/websocket.ts | 5 + .../ReferenceList/ReferenceList.tsx | 2 +- .../TodoListCard/TodoItem/TodoItem.tsx | 2 +- .../PairRoomCreateModal.stories.tsx | 2 +- .../PairRoomEntryModal/PairRoomEntryModal.tsx | 2 +- .../PairRoomButton/PairRoomButton.styles.ts | 2 +- .../MyPage/PairRoomButton/PairRoomButton.tsx | 2 +- .../PairRoom/ReferenceCard/ReferenceCard.tsx | 2 +- .../ReferenceList/ReferenceList.tsx | 2 +- .../PairRoom/TimerCard/TimerCard.tsx | 8 +- .../TodoListCard/TodoItem/TodoItem.tsx | 2 +- .../TodoListCard/TodoList/TodoList.tsx | 2 +- .../PairRoom/TodoListCard/TodoListCard.tsx | 2 +- .../AddPairModal/AddPairModal.tsx | 2 +- frontend/src/hooks/PairRoom/useDragAndDrop.ts | 2 +- frontend/src/hooks/PairRoom/usePairRoom.ts | 39 ++++++ .../src/hooks/PairRoom/usePairRoomValid.ts | 2 +- frontend/src/hooks/PairRoom/useTimer.ts | 124 +++++++++--------- .../hooks/_common/member/useSignInHandler.ts | 2 +- .../hooks/_common/member/useSignOutHandler.ts | 2 +- .../hooks/_common/member/useSignUpHandler.ts | 2 +- frontend/src/pages/Callback/Callback.tsx | 4 +- frontend/src/pages/PairRoom/PairRoom.tsx | 10 +- frontend/src/pages/PrivateRoutes.tsx | 2 +- .../useUserIsInPairRoomQuery.ts | 2 +- .../useUserRetrospectExistsQuery.ts | 2 +- .../src/queries/MyPage/useMemberMutation.ts | 2 +- .../src/queries/MyPage/useMyPairRoomsQuery.ts | 2 +- .../queries/MyPage/useMyRetrospectsQuery.ts | 2 +- .../queries/PairRoom/useCategoriesMutation.ts | 2 +- .../queries/PairRoom/useCategoriesQuery.ts | 2 +- .../queries/PairRoom/usePairRoomMutation.ts | 2 +- .../src/queries/PairRoom/usePairRoomQuery.ts | 2 +- .../queries/PairRoom/useReferencesMutation.ts | 2 +- .../queries/PairRoom/useReferencesQuery.ts | 2 +- .../src/queries/PairRoom/useTodosMutation.ts | 2 +- .../src/queries/PairRoom/useTodosQuery.ts | 2 +- .../PairRoomOnboarding/useBranchesMutation.ts | 2 +- .../PairRoomOnboarding/useBranchesQuery.ts | 2 +- .../useRepositoriesQuery.ts | 2 +- .../Retrospect/useRetrospectMutation.ts | 2 +- .../queries/Retrospect/useRetrospectQuery.ts | 2 +- 53 files changed, 170 insertions(+), 123 deletions(-) rename frontend/src/apis/{ => http}/category.ts (97%) rename frontend/src/apis/{ => http}/fetcher.ts (100%) rename frontend/src/apis/{ => http}/github.ts (100%) rename frontend/src/apis/{ => http}/member.ts (95%) rename frontend/src/apis/{ => http}/oauth.ts (96%) rename frontend/src/apis/{ => http}/pairRoom.ts (94%) rename frontend/src/apis/{ => http}/referenceLink.ts (97%) rename frontend/src/apis/{ => http}/retrospect.ts (95%) rename frontend/src/apis/{ => http}/timer.ts (76%) rename frontend/src/apis/{ => http}/todo.ts (97%) create mode 100644 frontend/src/apis/websocket/websocket.ts create mode 100644 frontend/src/hooks/PairRoom/usePairRoom.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 733f73b6a..b75038cfb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -25,8 +25,8 @@ import HowToPair from '@/components/Landing/HowToPair/HowToPair'; import useUserStore from '@/stores/userStore'; -import { getMember } from '@/apis/member'; -import { getIsUserLoggedIn } from '@/apis/oauth'; +import { getMember } from '@/apis/http/member'; +import { getIsUserLoggedIn } from '@/apis/http/oauth'; import GlobalStyles from './styles/Global.style'; import { theme } from './styles/theme'; diff --git a/frontend/src/apis/category.ts b/frontend/src/apis/http/category.ts similarity index 97% rename from frontend/src/apis/category.ts rename to frontend/src/apis/http/category.ts index 6a9e38c56..4ecd8d83e 100644 --- a/frontend/src/apis/category.ts +++ b/frontend/src/apis/http/category.ts @@ -1,4 +1,4 @@ -import fetcher from '@/apis/fetcher'; +import fetcher from '@/apis/http/fetcher'; import { ERROR_MESSAGES } from '@/constants/message'; diff --git a/frontend/src/apis/fetcher.ts b/frontend/src/apis/http/fetcher.ts similarity index 100% rename from frontend/src/apis/fetcher.ts rename to frontend/src/apis/http/fetcher.ts diff --git a/frontend/src/apis/github.ts b/frontend/src/apis/http/github.ts similarity index 100% rename from frontend/src/apis/github.ts rename to frontend/src/apis/http/github.ts diff --git a/frontend/src/apis/member.ts b/frontend/src/apis/http/member.ts similarity index 95% rename from frontend/src/apis/member.ts rename to frontend/src/apis/http/member.ts index 1e4f3e53e..f0a0639a6 100644 --- a/frontend/src/apis/member.ts +++ b/frontend/src/apis/http/member.ts @@ -1,5 +1,5 @@ -import fetcher from '@/apis/fetcher'; -import type { PairRoomStatus } from '@/apis/pairRoom'; +import fetcher from '@/apis/http/fetcher'; +import type { PairRoomStatus } from '@/apis/http/pairRoom'; import { ERROR_MESSAGES } from '@/constants/message'; diff --git a/frontend/src/apis/oauth.ts b/frontend/src/apis/http/oauth.ts similarity index 96% rename from frontend/src/apis/oauth.ts rename to frontend/src/apis/http/oauth.ts index a5d417e48..65305e929 100644 --- a/frontend/src/apis/oauth.ts +++ b/frontend/src/apis/http/oauth.ts @@ -1,4 +1,4 @@ -import fetcher from '@/apis/fetcher'; +import fetcher from '@/apis/http/fetcher'; import { ERROR_MESSAGES } from '@/constants/message'; diff --git a/frontend/src/apis/pairRoom.ts b/frontend/src/apis/http/pairRoom.ts similarity index 94% rename from frontend/src/apis/pairRoom.ts rename to frontend/src/apis/http/pairRoom.ts index 28d9ba64d..21c71af7b 100644 --- a/frontend/src/apis/pairRoom.ts +++ b/frontend/src/apis/http/pairRoom.ts @@ -1,8 +1,8 @@ import { Category } from '@/components/PairRoom/ReferenceCard/ReferenceCard.type'; -import fetcher from '@/apis/fetcher'; -import { Reference } from '@/apis/referenceLink'; -import { Todo } from '@/apis/todo'; +import fetcher from '@/apis/http/fetcher'; +import { Reference } from '@/apis/http/referenceLink'; +import { Todo } from '@/apis/http/todo'; import { ERROR_MESSAGES } from '@/constants/message'; diff --git a/frontend/src/apis/referenceLink.ts b/frontend/src/apis/http/referenceLink.ts similarity index 97% rename from frontend/src/apis/referenceLink.ts rename to frontend/src/apis/http/referenceLink.ts index 7600d8e26..36c915fcd 100644 --- a/frontend/src/apis/referenceLink.ts +++ b/frontend/src/apis/http/referenceLink.ts @@ -1,4 +1,4 @@ -import fetcher from '@/apis/fetcher'; +import fetcher from '@/apis/http/fetcher'; import { ERROR_MESSAGES } from '@/constants/message'; diff --git a/frontend/src/apis/retrospect.ts b/frontend/src/apis/http/retrospect.ts similarity index 95% rename from frontend/src/apis/retrospect.ts rename to frontend/src/apis/http/retrospect.ts index 43326d19a..5906e019f 100644 --- a/frontend/src/apis/retrospect.ts +++ b/frontend/src/apis/http/retrospect.ts @@ -1,4 +1,4 @@ -import fetcher from '@/apis/fetcher'; +import fetcher from '@/apis/http/fetcher'; import { ERROR_MESSAGES } from '@/constants/message'; diff --git a/frontend/src/apis/timer.ts b/frontend/src/apis/http/timer.ts similarity index 76% rename from frontend/src/apis/timer.ts rename to frontend/src/apis/http/timer.ts index 7a99e007a..8b08458a1 100644 --- a/frontend/src/apis/timer.ts +++ b/frontend/src/apis/http/timer.ts @@ -1,11 +1,6 @@ -import fetcher from '@/apis/fetcher'; +import fetcher from '@/apis/http/fetcher'; const API_URL = process.env.REACT_APP_API_URL; -const SOCKET_URL = process.env.REACT_SOCKET_API_URL; - -export const getConnection = (accessCode: string) => { - return new WebSocket(`${SOCKET_URL}/ws-connect?accesscode=${accessCode}`); -}; interface UpdateDurationRequest { duration: string; diff --git a/frontend/src/apis/todo.ts b/frontend/src/apis/http/todo.ts similarity index 97% rename from frontend/src/apis/todo.ts rename to frontend/src/apis/http/todo.ts index fe021ae46..6c66c9fcc 100644 --- a/frontend/src/apis/todo.ts +++ b/frontend/src/apis/http/todo.ts @@ -1,4 +1,4 @@ -import fetcher from '@/apis/fetcher'; +import fetcher from '@/apis/http/fetcher'; import { ERROR_MESSAGES } from '@/constants/message'; diff --git a/frontend/src/apis/websocket/websocket.ts b/frontend/src/apis/websocket/websocket.ts new file mode 100644 index 000000000..c13f931aa --- /dev/null +++ b/frontend/src/apis/websocket/websocket.ts @@ -0,0 +1,5 @@ +const SOCKET_URL = process.env.REACT_SOCKET_API_URL; + +export const getConnection = (accessCode: string) => { + return new WebSocket(`${SOCKET_URL}/ws-connect?accesscode=${accessCode}`); +}; diff --git a/frontend/src/components/CompletedPairRoom/ReferenceCard/ReferenceList/ReferenceList.tsx b/frontend/src/components/CompletedPairRoom/ReferenceCard/ReferenceList/ReferenceList.tsx index 114524120..cfff8896c 100644 --- a/frontend/src/components/CompletedPairRoom/ReferenceCard/ReferenceList/ReferenceList.tsx +++ b/frontend/src/components/CompletedPairRoom/ReferenceCard/ReferenceList/ReferenceList.tsx @@ -1,6 +1,6 @@ import { Link } from 'react-router-dom'; -import type { Reference } from '@/apis/referenceLink'; +import type { Reference } from '@/apis/http/referenceLink'; import * as S from './ReferenceList.styles'; diff --git a/frontend/src/components/CompletedPairRoom/TodoListCard/TodoItem/TodoItem.tsx b/frontend/src/components/CompletedPairRoom/TodoListCard/TodoItem/TodoItem.tsx index 8fb0bb134..9b6eda598 100644 --- a/frontend/src/components/CompletedPairRoom/TodoListCard/TodoItem/TodoItem.tsx +++ b/frontend/src/components/CompletedPairRoom/TodoListCard/TodoItem/TodoItem.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { Todo } from '@/apis/todo'; +import { Todo } from '@/apis/http/todo'; import useCopyClipBoard from '@/hooks/_common/useCopyClipboard'; diff --git a/frontend/src/components/Main/PairRoomCreateModal/PairRoomCreateModal.stories.tsx b/frontend/src/components/Main/PairRoomCreateModal/PairRoomCreateModal.stories.tsx index 5e4123e20..a24d529cd 100644 --- a/frontend/src/components/Main/PairRoomCreateModal/PairRoomCreateModal.stories.tsx +++ b/frontend/src/components/Main/PairRoomCreateModal/PairRoomCreateModal.stories.tsx @@ -13,7 +13,7 @@ type Story = StoryObj; export const Default: Story = { render: () => { - return console.log()} />; + return alert('닫기')} />; }, args: { isOpen: true, diff --git a/frontend/src/components/Main/PairRoomEntryModal/PairRoomEntryModal.tsx b/frontend/src/components/Main/PairRoomEntryModal/PairRoomEntryModal.tsx index 04dbf0a1a..6461e6af8 100644 --- a/frontend/src/components/Main/PairRoomEntryModal/PairRoomEntryModal.tsx +++ b/frontend/src/components/Main/PairRoomEntryModal/PairRoomEntryModal.tsx @@ -6,7 +6,7 @@ import { Modal } from '@/components/_common/Modal'; import useToastStore from '@/stores/toastStore'; -import { getPairRoomExists } from '@/apis/pairRoom'; +import { getPairRoomExists } from '@/apis/http/pairRoom'; import useClickEnterKey from '@/hooks/_common/customEvent/useClickEnterKey'; import useInput from '@/hooks/_common/useInput'; diff --git a/frontend/src/components/MyPage/PairRoomButton/PairRoomButton.styles.ts b/frontend/src/components/MyPage/PairRoomButton/PairRoomButton.styles.ts index 0bc01df28..eb9c9f086 100644 --- a/frontend/src/components/MyPage/PairRoomButton/PairRoomButton.styles.ts +++ b/frontend/src/components/MyPage/PairRoomButton/PairRoomButton.styles.ts @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; import { FaTrashAlt } from 'react-icons/fa'; import styled, { keyframes, css } from 'styled-components'; -import type { PairRoomStatus } from '@/apis/pairRoom'; +import type { PairRoomStatus } from '@/apis/http/pairRoom'; import { Z_INDEX } from '@/constants/style'; diff --git a/frontend/src/components/MyPage/PairRoomButton/PairRoomButton.tsx b/frontend/src/components/MyPage/PairRoomButton/PairRoomButton.tsx index 88da4fec7..f9566934a 100644 --- a/frontend/src/components/MyPage/PairRoomButton/PairRoomButton.tsx +++ b/frontend/src/components/MyPage/PairRoomButton/PairRoomButton.tsx @@ -3,7 +3,7 @@ import { IoIosArrowForward } from 'react-icons/io'; import ConfirmModal from '@/components/_common/ConfirmModal/ConfirmModal'; import Spinner from '@/components/_common/Spinner/Spinner'; -import type { PairRoomStatus } from '@/apis/pairRoom'; +import type { PairRoomStatus } from '@/apis/http/pairRoom'; import useModal from '@/hooks/_common/useModal'; diff --git a/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx b/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx index 3551f9a34..009326a33 100644 --- a/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx +++ b/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx @@ -7,7 +7,7 @@ import Header from '@/components/PairRoom/ReferenceCard/Header/Header'; import { Category } from '@/components/PairRoom/ReferenceCard/ReferenceCard.type'; import ReferenceList from '@/components/PairRoom/ReferenceCard/ReferenceList/ReferenceList'; -import { Reference } from '@/apis/referenceLink'; +import { Reference } from '@/apis/http/referenceLink'; import useModal from '@/hooks/_common/useModal'; diff --git a/frontend/src/components/PairRoom/ReferenceCard/ReferenceList/ReferenceList.tsx b/frontend/src/components/PairRoom/ReferenceCard/ReferenceList/ReferenceList.tsx index 7b85877ed..ce293226f 100644 --- a/frontend/src/components/PairRoom/ReferenceCard/ReferenceList/ReferenceList.tsx +++ b/frontend/src/components/PairRoom/ReferenceCard/ReferenceList/ReferenceList.tsx @@ -1,6 +1,6 @@ import { Link } from 'react-router-dom'; -import type { Reference } from '@/apis/referenceLink'; +import type { Reference } from '@/apis/http/referenceLink'; import useReferencesMutation from '@/queries/PairRoom/useReferencesMutation'; diff --git a/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx b/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx index 296fd9ee0..604aff487 100644 --- a/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx +++ b/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx @@ -16,17 +16,19 @@ import { theme } from '@/styles/theme'; import * as S from './TimerCard.styles'; interface TimerCardProps { + socket: WebSocket | null; accessCode: string; defaultTime: number; - defaultTimeleft: number; + defaultTimeLeft: number; onTimerStop: () => void; } -const TimerCard = ({ accessCode, defaultTime, defaultTimeleft, onTimerStop }: TimerCardProps) => { +const TimerCard = ({ socket, accessCode, defaultTime, defaultTimeLeft, onTimerStop }: TimerCardProps) => { const { timeLeft, isActive, handleStart, handlePause } = useTimer( + socket, accessCode, defaultTime, - defaultTimeleft, + defaultTimeLeft, onTimerStop, ); diff --git a/frontend/src/components/PairRoom/TodoListCard/TodoItem/TodoItem.tsx b/frontend/src/components/PairRoom/TodoListCard/TodoItem/TodoItem.tsx index 40d391701..d50f8ca86 100644 --- a/frontend/src/components/PairRoom/TodoListCard/TodoItem/TodoItem.tsx +++ b/frontend/src/components/PairRoom/TodoListCard/TodoItem/TodoItem.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import CheckBox from '@/components/_common/CheckBox/CheckBox'; -import { Todo } from '@/apis/todo'; +import { Todo } from '@/apis/http/todo'; import useCopyClipBoard from '@/hooks/_common/useCopyClipboard'; diff --git a/frontend/src/components/PairRoom/TodoListCard/TodoList/TodoList.tsx b/frontend/src/components/PairRoom/TodoListCard/TodoList/TodoList.tsx index a035ed491..79798799e 100644 --- a/frontend/src/components/PairRoom/TodoListCard/TodoList/TodoList.tsx +++ b/frontend/src/components/PairRoom/TodoListCard/TodoList/TodoList.tsx @@ -1,6 +1,6 @@ import TodoItem from '@/components/PairRoom/TodoListCard/TodoItem/TodoItem'; -import { Todo } from '@/apis/todo'; +import { Todo } from '@/apis/http/todo'; import useDragAndDrop from '@/hooks/PairRoom/useDragAndDrop'; diff --git a/frontend/src/components/PairRoom/TodoListCard/TodoListCard.tsx b/frontend/src/components/PairRoom/TodoListCard/TodoListCard.tsx index 20d1345fd..d9c8f1cce 100644 --- a/frontend/src/components/PairRoom/TodoListCard/TodoListCard.tsx +++ b/frontend/src/components/PairRoom/TodoListCard/TodoListCard.tsx @@ -8,7 +8,7 @@ import { PairRoomCard } from '@/components/PairRoom/PairRoomCard'; import Header from '@/components/PairRoom/TodoListCard/Header/Header'; import TodoList from '@/components/PairRoom/TodoListCard/TodoList/TodoList'; -import { Todo } from '@/apis/todo'; +import { Todo } from '@/apis/http/todo'; import useInput from '@/hooks/_common/useInput'; diff --git a/frontend/src/components/PairRoomOnboarding/AddPairModal/AddPairModal.tsx b/frontend/src/components/PairRoomOnboarding/AddPairModal/AddPairModal.tsx index e78609dbf..be404a081 100644 --- a/frontend/src/components/PairRoomOnboarding/AddPairModal/AddPairModal.tsx +++ b/frontend/src/components/PairRoomOnboarding/AddPairModal/AddPairModal.tsx @@ -6,7 +6,7 @@ import { Modal } from '@/components/_common/Modal'; import useToastStore from '@/stores/toastStore'; -import { getMemberName } from '@/apis/member'; +import { getMemberName } from '@/apis/http/member'; import useClickEnterKey from '@/hooks/_common/customEvent/useClickEnterKey'; import useInput from '@/hooks/_common/useInput'; diff --git a/frontend/src/hooks/PairRoom/useDragAndDrop.ts b/frontend/src/hooks/PairRoom/useDragAndDrop.ts index 5a05a33f8..31cedb970 100644 --- a/frontend/src/hooks/PairRoom/useDragAndDrop.ts +++ b/frontend/src/hooks/PairRoom/useDragAndDrop.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { Todo } from '@/apis/todo'; +import { Todo } from '@/apis/http/todo'; export type DragPosition = 'ABOVE' | 'BELOW'; diff --git a/frontend/src/hooks/PairRoom/usePairRoom.ts b/frontend/src/hooks/PairRoom/usePairRoom.ts new file mode 100644 index 000000000..c139141d0 --- /dev/null +++ b/frontend/src/hooks/PairRoom/usePairRoom.ts @@ -0,0 +1,39 @@ +import { useEffect, useRef } from 'react'; + +import useToastStore from '@/stores/toastStore'; + +import { getConnection } from '@/apis/websocket/websocket'; + +const usePairRoom = (accessCode: string) => { + const socketRef = useRef(null); + + const { addToast } = useToastStore(); + + const handleBeforeUnload = (event: BeforeUnloadEvent) => { + event.preventDefault(); + }; + + useEffect(() => { + const socket = getConnection(accessCode); + + socket.onopen = () => { + socketRef.current = socket; + }; + + socket.onerror = (error) => { + console.error(error); + addToast({ status: 'ERROR', message: `웹소켓 연결 과정에서 오류가 발생했습니다. ${error}` }); + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + + return () => { + socket.close(); + window.removeEventListener('beforeunload', handleBeforeUnload); + }; + }, []); + + return { socket: socketRef.current }; +}; + +export default usePairRoom; diff --git a/frontend/src/hooks/PairRoom/usePairRoomValid.ts b/frontend/src/hooks/PairRoom/usePairRoomValid.ts index 5ab4ea3f6..4174e645f 100644 --- a/frontend/src/hooks/PairRoom/usePairRoomValid.ts +++ b/frontend/src/hooks/PairRoom/usePairRoomValid.ts @@ -3,7 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom'; import useToastStore from '@/stores/toastStore'; -import { getPairRoomExists } from '@/apis/pairRoom'; +import { getPairRoomExists } from '@/apis/http/pairRoom'; const usePairRoomValid = (accessCode: string) => { const location = useLocation(); diff --git a/frontend/src/hooks/PairRoom/useTimer.ts b/frontend/src/hooks/PairRoom/useTimer.ts index 3641cdbf8..33a7d50f5 100644 --- a/frontend/src/hooks/PairRoom/useTimer.ts +++ b/frontend/src/hooks/PairRoom/useTimer.ts @@ -1,4 +1,4 @@ -import { useRef, useState, useEffect } from 'react'; +import { useState, useRef, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; @@ -7,18 +7,18 @@ import { AlarmSound } from '@/assets'; import useToastStore from '@/stores/toastStore'; -import { getConnection, startTimer, stopTimer } from '@/apis/timer'; +import { startTimer, stopTimer } from '@/apis/http/timer'; import useNotification from '@/hooks/PairRoom/useNotification'; import { QUERY_KEYS } from '@/constants/queryKeys'; -const EVENT_NAMES = { +const TIMER_EVENTS = { TIMER: 'timer', REMAINING_TIME: 'remaining-time', }; -const MESSAGES = { +const TIMER_MESSAGES = { COMPLETE: 'complete', START: 'start', RUNNING: 'running', @@ -26,16 +26,19 @@ const MESSAGES = { UPDATE: 'update', }; -const useTimer = (accessCode: string, defaultTime: number, defaultTimeleft: number, onTimerStop: () => void) => { - const navigate = useNavigate(); +const useTimer = ( + socket: WebSocket | null, + accessCode: string, + defaultTime: number, + defaultTimeLeft: number, + onTimerStop: () => void, +) => { + const [timeLeft, setTimeLeft] = useState(defaultTimeLeft); + const [isActive, setIsActive] = useState(false); + const navigate = useNavigate(); const queryClient = useQueryClient(); - const alarmAudio = useRef(new Audio(AlarmSound)); - // const timeoutCount = useRef(0); - - const [timeLeft, setTimeLeft] = useState(defaultTimeleft); - const [isActive, setIsActive] = useState(false); const { addToast } = useToastStore(); const { fireNotification } = useNotification(); @@ -45,100 +48,99 @@ const useTimer = (accessCode: string, defaultTime: number, defaultTimeleft: numb }; const handlePause = () => { - stopTimer(accessCode); + if (isActive) stopTimer(accessCode); }; const handleStop = () => { - addToast({ status: 'SUCCESS', message: '타이머가 종료되었습니다.' }); - setIsActive(false); setTimeLeft(defaultTime); onTimerStop(); - addToast({ status: 'INFO', message: '드라이버 / 내비게이터 역할을 바꿔 주세요!' }); - }; + alarmAudio.current.play(); + fireNotification('타이머가 끝났어요!', '드라이버 / 내비게이터 역할을 바꿔 주세요!', { + requireInteraction: true, + }); - const handleEvent = (eventName: string, eventData: string) => { - switch (eventName) { - case EVENT_NAMES.TIMER: - handleTimerEvent(eventData); - break; - case EVENT_NAMES.REMAINING_TIME: - handleRemainingTimeEvent(eventData); - break; - default: - console.warn(`Unhandled event: ${eventName}`); - } + addToast({ status: 'SUCCESS', message: '타이머가 종료되었습니다.' }); + addToast({ status: 'INFO', message: '드라이버 / 내비게이터 역할을 바꿔 주세요!' }); }; const handleTimerEvent = (eventData: string) => { switch (eventData) { - case MESSAGES.COMPLETE: + case TIMER_MESSAGES.COMPLETE: navigate(`/room/${accessCode}/retrospectForm`, { state: { valid: true } }); addToast({ status: 'WARNING', message: '페어룸이 종료되었습니다.' }); break; - case MESSAGES.START: - case MESSAGES.RUNNING: + + case TIMER_MESSAGES.START: + case TIMER_MESSAGES.RUNNING: setIsActive(true); addToast({ status: 'SUCCESS', message: '타이머가 시작되었습니다.' }); break; - case MESSAGES.PAUSE: + + case TIMER_MESSAGES.PAUSE: setIsActive(false); addToast({ status: 'WARNING', message: '타이머가 일시 정지되었습니다.' }); break; - case MESSAGES.UPDATE: + + case TIMER_MESSAGES.UPDATE: queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_PAIR_ROOM_TIMER] }); addToast({ status: 'WARNING', message: '타이머 시간이 변경되었습니다.' }); break; + default: - console.warn(`Unhandled timer event data: ${eventData}`); + addToast({ status: 'ERROR', message: '예상하지 못한 에러가 발생했습니다.' }); } }; const handleRemainingTimeEvent = (eventData: string) => { if (eventData === '0') { handleStop(); - alarmAudio.current.play(); - fireNotification('타이머가 끝났어요!', '드라이버 / 내비게이터 역할을 바꿔 주세요!', { - requireInteraction: true, - }); - } else { - setTimeLeft(Number(eventData)); + return; } + + setTimeLeft(Number(eventData)); }; - useEffect(() => { - const socket = getConnection(accessCode); + const handleTimer = (eventName: string, eventData: string) => { + switch (eventName) { + case TIMER_EVENTS.TIMER: + handleTimerEvent(eventData); + break; - const handleMessage = (event: MessageEvent) => { - console.log('Received event:', event.data); - const parsedData = JSON.parse(event.data); - handleEvent(parsedData.event, parsedData.data); - }; + case TIMER_EVENTS.REMAINING_TIME: + handleRemainingTimeEvent(eventData); + break; - const handleBeforeUnload = (event: BeforeUnloadEvent) => { - event.preventDefault(); - }; + default: + console.error(`Unhandled event: ${eventName}`); + addToast({ status: 'ERROR', message: '예상하지 못한 에러가 발생했습니다.' }); + } + }; + + const handleMessage = (event: MessageEvent) => { + const parsedData = JSON.parse(event.data); + handleTimer(parsedData.event, parsedData.data); + }; + + useEffect(() => { + if (!socket) return; socket.onopen = () => { - console.log('WebSocket connection opened'); socket.addEventListener('message', handleMessage as EventListener); }; - socket.onerror = (error) => { - console.error('WebSocket connection error:', error); - }; - - window.addEventListener('beforeunload', handleBeforeUnload); - return () => { - socket.removeEventListener('message', handleMessage as EventListener); - socket.close(); - window.removeEventListener('beforeunload', handleBeforeUnload); + if (socket) socket.removeEventListener('message', handleMessage as EventListener); }; - }, []); + }, [socket]); - return { timeLeft, isActive, handleStart, handlePause }; + return { + timeLeft, + isActive, + handleStart, + handlePause, + }; }; export default useTimer; diff --git a/frontend/src/hooks/_common/member/useSignInHandler.ts b/frontend/src/hooks/_common/member/useSignInHandler.ts index 1670106d7..d033ecc2c 100644 --- a/frontend/src/hooks/_common/member/useSignInHandler.ts +++ b/frontend/src/hooks/_common/member/useSignInHandler.ts @@ -1,4 +1,4 @@ -import { getSignInGithub } from '@/apis/oauth'; +import { getSignInGithub } from '@/apis/http/oauth'; const useSignInHandler = () => { const handleSignInGithub = async () => { diff --git a/frontend/src/hooks/_common/member/useSignOutHandler.ts b/frontend/src/hooks/_common/member/useSignOutHandler.ts index 6f451360e..383fef63c 100644 --- a/frontend/src/hooks/_common/member/useSignOutHandler.ts +++ b/frontend/src/hooks/_common/member/useSignOutHandler.ts @@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom'; import useUserStore from '@/stores/userStore'; -import { getSignOut } from '@/apis/member'; +import { getSignOut } from '@/apis/http/member'; const useSignOutHandler = () => { const navigate = useNavigate(); diff --git a/frontend/src/hooks/_common/member/useSignUpHandler.ts b/frontend/src/hooks/_common/member/useSignUpHandler.ts index bac0252a9..7444e910c 100644 --- a/frontend/src/hooks/_common/member/useSignUpHandler.ts +++ b/frontend/src/hooks/_common/member/useSignUpHandler.ts @@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom'; import useUserStore from '@/stores/userStore'; -import { addSignUp } from '@/apis/oauth'; +import { addSignUp } from '@/apis/http/oauth'; const useSignUpHandler = () => { const navigate = useNavigate(); diff --git a/frontend/src/pages/Callback/Callback.tsx b/frontend/src/pages/Callback/Callback.tsx index d83dd5512..c10261a50 100644 --- a/frontend/src/pages/Callback/Callback.tsx +++ b/frontend/src/pages/Callback/Callback.tsx @@ -5,8 +5,8 @@ import Spinner from '@/components/_common/Spinner/Spinner'; import useUserStore from '@/stores/userStore'; -import { getMember } from '@/apis/member'; -import { getSignInCallback } from '@/apis/oauth'; +import { getMember } from '@/apis/http/member'; +import { getSignInCallback } from '@/apis/http/oauth'; import * as S from './Callback.styles'; diff --git a/frontend/src/pages/PairRoom/PairRoom.tsx b/frontend/src/pages/PairRoom/PairRoom.tsx index 634658db7..6718a4668 100644 --- a/frontend/src/pages/PairRoom/PairRoom.tsx +++ b/frontend/src/pages/PairRoom/PairRoom.tsx @@ -11,6 +11,7 @@ import TimerCard from '@/components/PairRoom/TimerCard/TimerCard'; import TodoListCard from '@/components/PairRoom/TodoListCard/TodoListCard'; import useModal from '@/hooks/_common/useModal'; +import usePairRoom from '@/hooks/PairRoom/usePairRoom'; import usePairRoomMutation from '@/queries/PairRoom/usePairRoomMutation'; import usePairRoomQuery from '@/queries/PairRoom/usePairRoomQuery'; @@ -25,8 +26,6 @@ const PairRoom = () => { const [navigator, setNavigator] = useState(''); const [isCardOpen, setIsCardOpen] = useState(false); - const { isModalOpen, closeModal } = useModal(true); - const { driver: latestDriver, navigator: latestNavigator, @@ -42,6 +41,8 @@ const PairRoom = () => { const { updatePairRoleMutation } = usePairRoomMutation(); + const { isModalOpen, closeModal } = useModal(true); + useEffect(() => { if (status === 'COMPLETED') navigate(`/room/${accessCode}/completed`, { state: { valid: true }, replace: true }); }, [status]); @@ -51,6 +52,8 @@ const PairRoom = () => { setNavigator(latestNavigator); }, [latestDriver, latestNavigator]); + const { socket } = usePairRoom(accessCode || ''); + if (isFetching) { return ; } @@ -61,9 +64,10 @@ const PairRoom = () => { updatePairRoleMutation({ accessCode: accessCode || '' })} /> diff --git a/frontend/src/pages/PrivateRoutes.tsx b/frontend/src/pages/PrivateRoutes.tsx index 7c399eb0b..5e06ab575 100644 --- a/frontend/src/pages/PrivateRoutes.tsx +++ b/frontend/src/pages/PrivateRoutes.tsx @@ -5,7 +5,7 @@ import Loading from '@/pages/Loading/Loading'; import useToastStore from '@/stores/toastStore'; -import { getPairRoomExists } from '@/apis/pairRoom'; +import { getPairRoomExists } from '@/apis/http/pairRoom'; const PrivateRoutes = () => { const location = useLocation(); diff --git a/frontend/src/queries/CompletedPairRoom/useUserIsInPairRoomQuery.ts b/frontend/src/queries/CompletedPairRoom/useUserIsInPairRoomQuery.ts index 445d8032b..a84e51609 100644 --- a/frontend/src/queries/CompletedPairRoom/useUserIsInPairRoomQuery.ts +++ b/frontend/src/queries/CompletedPairRoom/useUserIsInPairRoomQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getUserIsInPairRoom } from '@/apis/member'; +import { getUserIsInPairRoom } from '@/apis/http/member'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/CompletedPairRoom/useUserRetrospectExistsQuery.ts b/frontend/src/queries/CompletedPairRoom/useUserRetrospectExistsQuery.ts index fd111da96..0ee1ca964 100644 --- a/frontend/src/queries/CompletedPairRoom/useUserRetrospectExistsQuery.ts +++ b/frontend/src/queries/CompletedPairRoom/useUserRetrospectExistsQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getUserRetrospectExists } from '@/apis/member'; +import { getUserRetrospectExists } from '@/apis/http/member'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/MyPage/useMemberMutation.ts b/frontend/src/queries/MyPage/useMemberMutation.ts index f2c5b15ce..c1b1e019e 100644 --- a/frontend/src/queries/MyPage/useMemberMutation.ts +++ b/frontend/src/queries/MyPage/useMemberMutation.ts @@ -5,7 +5,7 @@ import { useMutation } from '@tanstack/react-query'; import useToastStore from '@/stores/toastStore'; import useUserStore from '@/stores/userStore'; -import { deleteMember } from '@/apis/member'; +import { deleteMember } from '@/apis/http/member'; const useMemberMutation = () => { const navigate = useNavigate(); diff --git a/frontend/src/queries/MyPage/useMyPairRoomsQuery.ts b/frontend/src/queries/MyPage/useMyPairRoomsQuery.ts index 8cf2e2503..7a633b896 100644 --- a/frontend/src/queries/MyPage/useMyPairRoomsQuery.ts +++ b/frontend/src/queries/MyPage/useMyPairRoomsQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getMyPairRooms } from '@/apis/member'; +import { getMyPairRooms } from '@/apis/http/member'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/MyPage/useMyRetrospectsQuery.ts b/frontend/src/queries/MyPage/useMyRetrospectsQuery.ts index acb9c5e25..a5cd9357d 100644 --- a/frontend/src/queries/MyPage/useMyRetrospectsQuery.ts +++ b/frontend/src/queries/MyPage/useMyRetrospectsQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getMyRetrospects } from '@/apis/member'; +import { getMyRetrospects } from '@/apis/http/member'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoom/useCategoriesMutation.ts b/frontend/src/queries/PairRoom/useCategoriesMutation.ts index 563816440..00199ed0f 100644 --- a/frontend/src/queries/PairRoom/useCategoriesMutation.ts +++ b/frontend/src/queries/PairRoom/useCategoriesMutation.ts @@ -2,7 +2,7 @@ import { useQueryClient, useMutation } from '@tanstack/react-query'; import useToastStore from '@/stores/toastStore'; -import { addCategory, updateCategory, deleteCategory } from '@/apis/category'; +import { addCategory, updateCategory, deleteCategory } from '@/apis/http/category'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoom/useCategoriesQuery.ts b/frontend/src/queries/PairRoom/useCategoriesQuery.ts index b57c931d1..9d1f08e64 100644 --- a/frontend/src/queries/PairRoom/useCategoriesQuery.ts +++ b/frontend/src/queries/PairRoom/useCategoriesQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getCategories } from '@/apis/category'; +import { getCategories } from '@/apis/http/category'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoom/usePairRoomMutation.ts b/frontend/src/queries/PairRoom/usePairRoomMutation.ts index 0dbd7f5ba..d1410f5da 100644 --- a/frontend/src/queries/PairRoom/usePairRoomMutation.ts +++ b/frontend/src/queries/PairRoom/usePairRoomMutation.ts @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import useToastStore from '@/stores/toastStore'; -import { addPairRoom, updatePairRole, updatePairRoomStatus, deletePairRoom } from '@/apis/pairRoom'; +import { addPairRoom, updatePairRole, updatePairRoomStatus, deletePairRoom } from '@/apis/http/pairRoom'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoom/usePairRoomQuery.ts b/frontend/src/queries/PairRoom/usePairRoomQuery.ts index 445dfb91f..509960860 100644 --- a/frontend/src/queries/PairRoom/usePairRoomQuery.ts +++ b/frontend/src/queries/PairRoom/usePairRoomQuery.ts @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { getPairRoom } from '@/apis/pairRoom'; +import { getPairRoom } from '@/apis/http/pairRoom'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoom/useReferencesMutation.ts b/frontend/src/queries/PairRoom/useReferencesMutation.ts index dd4aa2aa1..bfb15b1f0 100644 --- a/frontend/src/queries/PairRoom/useReferencesMutation.ts +++ b/frontend/src/queries/PairRoom/useReferencesMutation.ts @@ -2,7 +2,7 @@ import { useQueryClient, useMutation } from '@tanstack/react-query'; import useToastStore from '@/stores/toastStore'; -import { addReferenceLink, deleteReferenceLink } from '@/apis/referenceLink'; +import { addReferenceLink, deleteReferenceLink } from '@/apis/http/referenceLink'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoom/useReferencesQuery.ts b/frontend/src/queries/PairRoom/useReferencesQuery.ts index 0ee9685e0..2ed07cd9f 100644 --- a/frontend/src/queries/PairRoom/useReferencesQuery.ts +++ b/frontend/src/queries/PairRoom/useReferencesQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getReferenceLinks } from '@/apis/referenceLink'; +import { getReferenceLinks } from '@/apis/http/referenceLink'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoom/useTodosMutation.ts b/frontend/src/queries/PairRoom/useTodosMutation.ts index 5ddaa84cf..68f4f6ed8 100644 --- a/frontend/src/queries/PairRoom/useTodosMutation.ts +++ b/frontend/src/queries/PairRoom/useTodosMutation.ts @@ -2,7 +2,7 @@ import { useQueryClient, useMutation } from '@tanstack/react-query'; import useToastStore from '@/stores/toastStore'; -import { addTodos, updateOrder, updateChecked, updateContents, deleteTodo } from '@/apis/todo'; +import { addTodos, updateOrder, updateChecked, updateContents, deleteTodo } from '@/apis/http/todo'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoom/useTodosQuery.ts b/frontend/src/queries/PairRoom/useTodosQuery.ts index 360a1ca27..058e80d48 100644 --- a/frontend/src/queries/PairRoom/useTodosQuery.ts +++ b/frontend/src/queries/PairRoom/useTodosQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getTodos } from '@/apis/todo'; +import { getTodos } from '@/apis/http/todo'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoomOnboarding/useBranchesMutation.ts b/frontend/src/queries/PairRoomOnboarding/useBranchesMutation.ts index 8e86cef0f..ffabfac26 100644 --- a/frontend/src/queries/PairRoomOnboarding/useBranchesMutation.ts +++ b/frontend/src/queries/PairRoomOnboarding/useBranchesMutation.ts @@ -2,7 +2,7 @@ import { useMutation } from '@tanstack/react-query'; import useToastStore from '@/stores/toastStore'; -import { addBranch, getSHAforMain } from '@/apis/github'; +import { addBranch, getSHAforMain } from '@/apis/http/github'; const useBranchesMutation = () => { const { addToast } = useToastStore(); diff --git a/frontend/src/queries/PairRoomOnboarding/useBranchesQuery.ts b/frontend/src/queries/PairRoomOnboarding/useBranchesQuery.ts index affb89840..d856283c2 100644 --- a/frontend/src/queries/PairRoomOnboarding/useBranchesQuery.ts +++ b/frontend/src/queries/PairRoomOnboarding/useBranchesQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getBranches } from '@/apis/github'; +import { getBranches } from '@/apis/http/github'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/PairRoomOnboarding/useRepositoriesQuery.ts b/frontend/src/queries/PairRoomOnboarding/useRepositoriesQuery.ts index e0ef051f6..7bd944a77 100644 --- a/frontend/src/queries/PairRoomOnboarding/useRepositoriesQuery.ts +++ b/frontend/src/queries/PairRoomOnboarding/useRepositoriesQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getRepositories } from '@/apis/github'; +import { getRepositories } from '@/apis/http/github'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/Retrospect/useRetrospectMutation.ts b/frontend/src/queries/Retrospect/useRetrospectMutation.ts index 817ea56e7..07da49655 100644 --- a/frontend/src/queries/Retrospect/useRetrospectMutation.ts +++ b/frontend/src/queries/Retrospect/useRetrospectMutation.ts @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import useToastStore from '@/stores/toastStore'; -import { addRetrospect, deleteRetrospect } from '@/apis/retrospect'; +import { addRetrospect, deleteRetrospect } from '@/apis/http/retrospect'; import { QUERY_KEYS } from '@/constants/queryKeys'; diff --git a/frontend/src/queries/Retrospect/useRetrospectQuery.ts b/frontend/src/queries/Retrospect/useRetrospectQuery.ts index 9a20b5f43..b23a59ae4 100644 --- a/frontend/src/queries/Retrospect/useRetrospectQuery.ts +++ b/frontend/src/queries/Retrospect/useRetrospectQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getRetrospect } from '@/apis/retrospect'; +import { getRetrospect } from '@/apis/http/retrospect'; import { QUERY_KEYS } from '@/constants/queryKeys'; From 4edfc9c0467835d5452566488460c021dfd53071 Mon Sep 17 00:00:00 2001 From: anttiey Date: Thu, 16 Jan 2025 15:56:19 +0900 Subject: [PATCH 2/4] =?UTF-8?q?:recycle:=20=EC=9B=B9=EC=86=8C=EC=BC=93=20S?= =?UTF-8?q?TOMP=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 2 + frontend/src/apis/http/pairRoom.ts | 2 +- frontend/src/apis/websocket/websocket.ts | 6 +- .../PairRoom/TimerCard/TimerCard.tsx | 7 +- frontend/src/hooks/PairRoom/usePairRoom.ts | 24 ++++-- frontend/src/hooks/PairRoom/useTimer.ts | 83 +++++++------------ frontend/src/hooks/PairRoom/useTodo.ts | 18 ++++ frontend/src/pages/PairRoom/PairRoom.tsx | 4 +- frontend/yarn.lock | 38 ++------- 9 files changed, 88 insertions(+), 96 deletions(-) create mode 100644 frontend/src/hooks/PairRoom/useTodo.ts diff --git a/frontend/package.json b/frontend/package.json index 4cecf2606..19dc2085f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@octokit/rest": "^21.0.1", "@sentry/react": "^8.22.0", "@sentry/webpack-plugin": "^2.21.1", + "@stomp/stompjs": "^7.0.0", "@tanstack/react-query": "^5.51.11", "dotenv-webpack": "^8.1.0", "react": "^18.3.1", @@ -27,6 +28,7 @@ "react-icons": "^5.3.0", "react-router-dom": "^6.25.1", "styled-components": "^6.1.12", + "ws": "^8.18.0", "zustand": "^4.5.4" }, "devDependencies": { diff --git a/frontend/src/apis/http/pairRoom.ts b/frontend/src/apis/http/pairRoom.ts index 21c71af7b..e86aec4a7 100644 --- a/frontend/src/apis/http/pairRoom.ts +++ b/frontend/src/apis/http/pairRoom.ts @@ -100,7 +100,7 @@ export const updatePairRoomStatus = async ({ accessCode }: UpdatePairRoomStatusR }; export const deletePairRoom = async ({ accessCode }: { accessCode: string }) => { - await fetcher.delete({ + await fetcher.patch({ url: `${API_URL}/pair-room/${accessCode}`, errorMessage: ERROR_MESSAGES.DELETE_PAIR_ROOM, }); diff --git a/frontend/src/apis/websocket/websocket.ts b/frontend/src/apis/websocket/websocket.ts index c13f931aa..6c3bb2ec7 100644 --- a/frontend/src/apis/websocket/websocket.ts +++ b/frontend/src/apis/websocket/websocket.ts @@ -1,5 +1,7 @@ +import { Client } from '@stomp/stompjs'; + const SOCKET_URL = process.env.REACT_SOCKET_API_URL; -export const getConnection = (accessCode: string) => { - return new WebSocket(`${SOCKET_URL}/ws-connect?accesscode=${accessCode}`); +export const getConnection = () => { + return new Client({ brokerURL: `${SOCKET_URL}/ws-connect` }); }; diff --git a/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx b/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx index 604aff487..68986f18d 100644 --- a/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx +++ b/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx @@ -1,5 +1,6 @@ import { useRef } from 'react'; +import { Client } from '@stomp/stompjs'; import { FaPause, FaPlay } from 'react-icons/fa6'; import IconButton from '@/components/_common/IconButton/IconButton'; @@ -16,16 +17,16 @@ import { theme } from '@/styles/theme'; import * as S from './TimerCard.styles'; interface TimerCardProps { - socket: WebSocket | null; + client: Client | null; accessCode: string; defaultTime: number; defaultTimeLeft: number; onTimerStop: () => void; } -const TimerCard = ({ socket, accessCode, defaultTime, defaultTimeLeft, onTimerStop }: TimerCardProps) => { +const TimerCard = ({ client, accessCode, defaultTime, defaultTimeLeft, onTimerStop }: TimerCardProps) => { const { timeLeft, isActive, handleStart, handlePause } = useTimer( - socket, + client, accessCode, defaultTime, defaultTimeLeft, diff --git a/frontend/src/hooks/PairRoom/usePairRoom.ts b/frontend/src/hooks/PairRoom/usePairRoom.ts index c139141d0..f7acaecc8 100644 --- a/frontend/src/hooks/PairRoom/usePairRoom.ts +++ b/frontend/src/hooks/PairRoom/usePairRoom.ts @@ -1,11 +1,13 @@ import { useEffect, useRef } from 'react'; +import { Client } from '@stomp/stompjs'; + import useToastStore from '@/stores/toastStore'; import { getConnection } from '@/apis/websocket/websocket'; -const usePairRoom = (accessCode: string) => { - const socketRef = useRef(null); +const usePairRoom = () => { + const stompClient = useRef(null); const { addToast } = useToastStore(); @@ -14,26 +16,32 @@ const usePairRoom = (accessCode: string) => { }; useEffect(() => { - const socket = getConnection(accessCode); + const client = getConnection(); + + client.onConnect = () => { + stompClient.current = client; + }; - socket.onopen = () => { - socketRef.current = socket; + client.onDisconnect = () => { + stompClient.current = null; }; - socket.onerror = (error) => { + client.onStompError = (error) => { console.error(error); addToast({ status: 'ERROR', message: `웹소켓 연결 과정에서 오류가 발생했습니다. ${error}` }); }; window.addEventListener('beforeunload', handleBeforeUnload); + client.activate(); + return () => { - socket.close(); + client.deactivate(); window.removeEventListener('beforeunload', handleBeforeUnload); }; }, []); - return { socket: socketRef.current }; + return { client: stompClient.current }; }; export default usePairRoom; diff --git a/frontend/src/hooks/PairRoom/useTimer.ts b/frontend/src/hooks/PairRoom/useTimer.ts index 33a7d50f5..2fc055087 100644 --- a/frontend/src/hooks/PairRoom/useTimer.ts +++ b/frontend/src/hooks/PairRoom/useTimer.ts @@ -1,6 +1,7 @@ import { useState, useRef, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { Client, Message } from '@stomp/stompjs'; import { useQueryClient } from '@tanstack/react-query'; import { AlarmSound } from '@/assets'; @@ -13,12 +14,7 @@ import useNotification from '@/hooks/PairRoom/useNotification'; import { QUERY_KEYS } from '@/constants/queryKeys'; -const TIMER_EVENTS = { - TIMER: 'timer', - REMAINING_TIME: 'remaining-time', -}; - -const TIMER_MESSAGES = { +const STATUS = { COMPLETE: 'complete', START: 'start', RUNNING: 'running', @@ -27,7 +23,7 @@ const TIMER_MESSAGES = { }; const useTimer = ( - socket: WebSocket | null, + client: Client | null, accessCode: string, defaultTime: number, defaultTimeLeft: number, @@ -65,25 +61,34 @@ const useTimer = ( addToast({ status: 'INFO', message: '드라이버 / 내비게이터 역할을 바꿔 주세요!' }); }; - const handleTimerEvent = (eventData: string) => { - switch (eventData) { - case TIMER_MESSAGES.COMPLETE: + const handleTimerEvent = (timeLeft: number) => { + if (timeLeft === 0) { + handleStop(); + return; + } + + setTimeLeft(timeLeft); + }; + + const handleTimerStatusEvent = (status: string) => { + switch (status) { + case STATUS.COMPLETE: navigate(`/room/${accessCode}/retrospectForm`, { state: { valid: true } }); addToast({ status: 'WARNING', message: '페어룸이 종료되었습니다.' }); break; - case TIMER_MESSAGES.START: - case TIMER_MESSAGES.RUNNING: + case STATUS.START: + case STATUS.RUNNING: setIsActive(true); addToast({ status: 'SUCCESS', message: '타이머가 시작되었습니다.' }); break; - case TIMER_MESSAGES.PAUSE: + case STATUS.PAUSE: setIsActive(false); addToast({ status: 'WARNING', message: '타이머가 일시 정지되었습니다.' }); break; - case TIMER_MESSAGES.UPDATE: + case STATUS.UPDATE: queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_PAIR_ROOM_TIMER] }); addToast({ status: 'WARNING', message: '타이머 시간이 변경되었습니다.' }); break; @@ -93,47 +98,23 @@ const useTimer = ( } }; - const handleRemainingTimeEvent = (eventData: string) => { - if (eventData === '0') { - handleStop(); - return; - } - - setTimeLeft(Number(eventData)); - }; - - const handleTimer = (eventName: string, eventData: string) => { - switch (eventName) { - case TIMER_EVENTS.TIMER: - handleTimerEvent(eventData); - break; - - case TIMER_EVENTS.REMAINING_TIME: - handleRemainingTimeEvent(eventData); - break; - - default: - console.error(`Unhandled event: ${eventName}`); - addToast({ status: 'ERROR', message: '예상하지 못한 에러가 발생했습니다.' }); - } - }; - - const handleMessage = (event: MessageEvent) => { - const parsedData = JSON.parse(event.data); - handleTimer(parsedData.event, parsedData.data); - }; - useEffect(() => { - if (!socket) return; - - socket.onopen = () => { - socket.addEventListener('message', handleMessage as EventListener); - }; + if (client) { + client.subscribe(`/topic/${accessCode}/timer`, (message: Message) => + handleTimerEvent(JSON.parse(message.body).data), + ); + client.subscribe(`/topic/${accessCode}/timer/status`, (message: Message) => + handleTimerStatusEvent(JSON.parse(message.body).data), + ); + } return () => { - if (socket) socket.removeEventListener('message', handleMessage as EventListener); + if (client) { + client.unsubscribe('/timer'); + client.unsubscribe('/timer/status'); + } }; - }, [socket]); + }, [client]); return { timeLeft, diff --git a/frontend/src/hooks/PairRoom/useTodo.ts b/frontend/src/hooks/PairRoom/useTodo.ts new file mode 100644 index 000000000..8933fc579 --- /dev/null +++ b/frontend/src/hooks/PairRoom/useTodo.ts @@ -0,0 +1,18 @@ +import { useState } from 'react'; + +import { Todo } from '@/components/PairRoom/TodoListCard/TodoListCard.type'; + +const TODO_MESSAGES = { + GET: 'get', + POST: 'post', + PUT: 'put', + DELETE: 'delete', +}; + +const useTodo = (client: Client | null, accessCode: string) => { + const [todos, setTodos] = useState([]); + + return { todos, setTodos }; +}; + +export default useTodo; diff --git a/frontend/src/pages/PairRoom/PairRoom.tsx b/frontend/src/pages/PairRoom/PairRoom.tsx index 6718a4668..7f2f75751 100644 --- a/frontend/src/pages/PairRoom/PairRoom.tsx +++ b/frontend/src/pages/PairRoom/PairRoom.tsx @@ -52,7 +52,7 @@ const PairRoom = () => { setNavigator(latestNavigator); }, [latestDriver, latestNavigator]); - const { socket } = usePairRoom(accessCode || ''); + const { client } = usePairRoom(accessCode || ''); if (isFetching) { return ; @@ -64,7 +64,7 @@ const PairRoom = () => { Date: Wed, 22 Jan 2025 16:33:23 +0900 Subject: [PATCH 3/4] =?UTF-8?q?:recycle:=20=ED=88=AC=EB=91=90=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20STOMP=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/apis/websocket/todo.ts | 21 +++++++++ frontend/src/apis/websocket/websocket.ts | 26 ++++++++++- .../ReferenceCard/ReferenceCard.stories.tsx | 4 +- .../PairRoom/ReferenceCard/ReferenceCard.tsx | 7 ++- .../PairRoom/TimerCard/TimerCard.tsx | 13 +----- .../TodoListCard/TodoItem/TodoItem.tsx | 11 +++-- .../TodoListCard/TodoList/TodoList.tsx | 9 ++-- .../PairRoom/TodoListCard/TodoListCard.tsx | 21 +++++---- .../TodoListCard/TodoListCard.type.ts | 6 --- frontend/src/hooks/PairRoom/usePairRoom.ts | 18 ++++---- frontend/src/hooks/PairRoom/useTimer.ts | 46 +++++++++---------- frontend/src/hooks/PairRoom/useTodo.ts | 34 +++++++++----- frontend/src/pages/PairRoom/PairRoom.tsx | 14 +++--- frontend/src/pages/PrivateRoutes.tsx | 3 ++ .../src/queries/PairRoom/useTimerMutation.ts | 2 +- frontend/src/stores/socketStore.ts | 22 +++++++++ 16 files changed, 165 insertions(+), 92 deletions(-) create mode 100644 frontend/src/apis/websocket/todo.ts delete mode 100644 frontend/src/components/PairRoom/TodoListCard/TodoListCard.type.ts create mode 100644 frontend/src/stores/socketStore.ts diff --git a/frontend/src/apis/websocket/todo.ts b/frontend/src/apis/websocket/todo.ts new file mode 100644 index 000000000..ea6d142ce --- /dev/null +++ b/frontend/src/apis/websocket/todo.ts @@ -0,0 +1,21 @@ +import { Client } from '@stomp/stompjs'; + +import { publishMessage } from '@/apis/websocket/websocket'; + +export const publishTodoMessage = { + add: (client: Client | null, accessCode: string, contents: string) => { + publishMessage(client, `/topic/${accessCode}/todo/add`, { contents }); + }, + updateContents: (client: Client | null, accessCode: string, todoId: number, contents: string) => { + publishMessage(client, `/topic/${accessCode}/todo/update/${todoId}/contents`, { contents }); + }, + updateOrder: (client: Client | null, accessCode: string, todoId: number, order: number) => { + publishMessage(client, `/topic/${accessCode}/todo/update/${todoId}/order`, { order }); + }, + updateChecked: (client: Client | null, accessCode: string, todoId: number) => { + publishMessage(client, `/topic/${accessCode}/todo/update/${todoId}/checked`); + }, + delete: (client: Client | null, accessCode: string, todoId: number) => { + publishMessage(client, `/topic/${accessCode}/todo/delete/${todoId}`); + }, +}; diff --git a/frontend/src/apis/websocket/websocket.ts b/frontend/src/apis/websocket/websocket.ts index 6c3bb2ec7..ddfa66b78 100644 --- a/frontend/src/apis/websocket/websocket.ts +++ b/frontend/src/apis/websocket/websocket.ts @@ -1,7 +1,31 @@ -import { Client } from '@stomp/stompjs'; +import { Client, Message } from '@stomp/stompjs'; const SOCKET_URL = process.env.REACT_SOCKET_API_URL; export const getConnection = () => { return new Client({ brokerURL: `${SOCKET_URL}/ws-connect` }); }; + +export const subscribeTopic = (client: Client | null, destination: string, handler: (body: T) => void) => { + if (!client?.connected) { + console.error('웹소켓 연결에 실패했습니다.'); + return; + } + + client.subscribe(destination, (message: Message) => { + const body: T = JSON.parse(message.body); + handler(body); + }); +}; + +export const publishMessage = (client: Client | null, destination: string, contents?: object) => { + if (!client?.connected) { + console.error('[ERROR] 서버와 통신에 실패했습니다.'); + return; + } + + client.publish({ + destination: destination, + body: JSON.stringify(contents), + }); +}; diff --git a/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.stories.tsx b/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.stories.tsx index d50c27e79..4d252e29c 100644 --- a/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.stories.tsx +++ b/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.stories.tsx @@ -12,7 +12,5 @@ export default meta; type Story = StoryObj; export const Default: Story = { - render: () => ( - {}} references={[]} categories={[]} /> - ), + render: () => {}} references={[]} categories={[]} />, }; diff --git a/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx b/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx index 009326a33..96b867c53 100644 --- a/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx +++ b/frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx @@ -7,6 +7,8 @@ import Header from '@/components/PairRoom/ReferenceCard/Header/Header'; import { Category } from '@/components/PairRoom/ReferenceCard/ReferenceCard.type'; import ReferenceList from '@/components/PairRoom/ReferenceCard/ReferenceList/ReferenceList'; +import useSocketStore from '@/stores/socketStore'; + import { Reference } from '@/apis/http/referenceLink'; import useModal from '@/hooks/_common/useModal'; @@ -18,14 +20,15 @@ import { findValueById } from '@/utils/findOption'; import * as S from './ReferenceCard.styles'; interface ReferenceCardProps { - accessCode: string; isOpen: boolean; toggleIsOpen: () => void; references: Reference[]; categories: Category[]; } -const ReferenceCard = ({ accessCode, isOpen, toggleIsOpen, references, categories }: ReferenceCardProps) => { +const ReferenceCard = ({ isOpen, toggleIsOpen, references, categories }: ReferenceCardProps) => { + const { accessCode } = useSocketStore(); + const [selectedCategoryId, setSelectedCategoryId] = useState(DEFAULT_CATEGORY_ID); const { isModalOpen, openModal, closeModal } = useModal(); diff --git a/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx b/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx index 68986f18d..a332e9d93 100644 --- a/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx +++ b/frontend/src/components/PairRoom/TimerCard/TimerCard.tsx @@ -1,6 +1,5 @@ import { useRef } from 'react'; -import { Client } from '@stomp/stompjs'; import { FaPause, FaPlay } from 'react-icons/fa6'; import IconButton from '@/components/_common/IconButton/IconButton'; @@ -17,21 +16,13 @@ import { theme } from '@/styles/theme'; import * as S from './TimerCard.styles'; interface TimerCardProps { - client: Client | null; - accessCode: string; defaultTime: number; defaultTimeLeft: number; onTimerStop: () => void; } -const TimerCard = ({ client, accessCode, defaultTime, defaultTimeLeft, onTimerStop }: TimerCardProps) => { - const { timeLeft, isActive, handleStart, handlePause } = useTimer( - client, - accessCode, - defaultTime, - defaultTimeLeft, - onTimerStop, - ); +const TimerCard = ({ defaultTime, defaultTimeLeft, onTimerStop }: TimerCardProps) => { + const { timeLeft, isActive, handleStart, handlePause } = useTimer(defaultTime, defaultTimeLeft, onTimerStop); const timeLeftRef = useRef(timeLeft); timeLeftRef.current = timeLeft; diff --git a/frontend/src/components/PairRoom/TodoListCard/TodoItem/TodoItem.tsx b/frontend/src/components/PairRoom/TodoListCard/TodoItem/TodoItem.tsx index d50f8ca86..ac7cd4b42 100644 --- a/frontend/src/components/PairRoom/TodoListCard/TodoItem/TodoItem.tsx +++ b/frontend/src/components/PairRoom/TodoListCard/TodoItem/TodoItem.tsx @@ -2,12 +2,13 @@ import { useState } from 'react'; import CheckBox from '@/components/_common/CheckBox/CheckBox'; +import useSocketStore from '@/stores/socketStore'; + import { Todo } from '@/apis/http/todo'; +import { publishTodoMessage } from '@/apis/websocket/todo'; import useCopyClipBoard from '@/hooks/_common/useCopyClipboard'; -import useTodosMutation from '@/queries/PairRoom/useTodosMutation'; - import * as S from './TodoItem.styles'; interface TodoItemProps { @@ -22,7 +23,7 @@ const TodoItem = ({ todo, isDraggedOver, onDragStart, onDragEnter, onDrop }: Tod const [isIconHovered, setIsIconHovered] = useState(false); const [, onCopy] = useCopyClipBoard(); - const { updateCheckedMutation, deleteTodoMutation } = useTodosMutation(); + const { client, accessCode } = useSocketStore(); const { id, isChecked, content } = todo; @@ -38,7 +39,7 @@ const TodoItem = ({ todo, isDraggedOver, onDragStart, onDragEnter, onDrop }: Tod onDragEnd={onDrop} > - updateCheckedMutation({ todoId: id })} /> + publishTodoMessage.updateChecked(client, accessCode, id)} />

{content}

@@ -52,7 +53,7 @@ const TodoItem = ({ todo, isDraggedOver, onDragStart, onDragEnter, onDrop }: Tod $isChecked={isChecked} onMouseEnter={() => setIsIconHovered(true)} onMouseLeave={() => setIsIconHovered(false)} - onClick={() => deleteTodoMutation({ todoId: id })} + onClick={() => publishTodoMessage.delete(client, accessCode, id)} /> diff --git a/frontend/src/components/PairRoom/TodoListCard/TodoList/TodoList.tsx b/frontend/src/components/PairRoom/TodoListCard/TodoList/TodoList.tsx index 79798799e..ae01ad379 100644 --- a/frontend/src/components/PairRoom/TodoListCard/TodoList/TodoList.tsx +++ b/frontend/src/components/PairRoom/TodoListCard/TodoList/TodoList.tsx @@ -1,11 +1,12 @@ import TodoItem from '@/components/PairRoom/TodoListCard/TodoItem/TodoItem'; +import useSocketStore from '@/stores/socketStore'; + import { Todo } from '@/apis/http/todo'; +import { publishTodoMessage } from '@/apis/websocket/todo'; import useDragAndDrop from '@/hooks/PairRoom/useDragAndDrop'; -import useTodosMutation from '@/queries/PairRoom/useTodosMutation'; - import * as S from './TodoList.styles'; interface TodoListProps { @@ -13,10 +14,10 @@ interface TodoListProps { } const TodoList = ({ todos }: TodoListProps) => { - const { updateOrderMutation } = useTodosMutation(); + const { client, accessCode } = useSocketStore(); const handleUpdateOrder = (todoId: number, order: number) => { - updateOrderMutation({ todoId, order }); + publishTodoMessage.updateOrder(client, accessCode, todoId, order); }; const { dragOverItem, handleDragStart, handleDragEnter, handleDrop } = useDragAndDrop(todos, handleUpdateOrder); diff --git a/frontend/src/components/PairRoom/TodoListCard/TodoListCard.tsx b/frontend/src/components/PairRoom/TodoListCard/TodoListCard.tsx index d9c8f1cce..89343c303 100644 --- a/frontend/src/components/PairRoom/TodoListCard/TodoListCard.tsx +++ b/frontend/src/components/PairRoom/TodoListCard/TodoListCard.tsx @@ -1,5 +1,3 @@ -import { useParams } from 'react-router-dom'; - import { LuPlus } from 'react-icons/lu'; import Button from '@/components/_common/Button/Button'; @@ -8,29 +6,32 @@ import { PairRoomCard } from '@/components/PairRoom/PairRoomCard'; import Header from '@/components/PairRoom/TodoListCard/Header/Header'; import TodoList from '@/components/PairRoom/TodoListCard/TodoList/TodoList'; +import useSocketStore from '@/stores/socketStore'; + import { Todo } from '@/apis/http/todo'; +import { publishTodoMessage } from '@/apis/websocket/todo'; import useInput from '@/hooks/_common/useInput'; - -import useTodosMutation from '@/queries/PairRoom/useTodosMutation'; +import useTodo from '@/hooks/PairRoom/useTodo'; import * as S from './TodoListCard.styles'; interface TodoListCardProps { isOpen: boolean; toggleIsOpen: () => void; - todos: Todo[]; + defaultTodos: Todo[]; } -const TodoListCard = ({ isOpen, toggleIsOpen, todos }: TodoListCardProps) => { - const { accessCode } = useParams(); - +const TodoListCard = ({ isOpen, toggleIsOpen, defaultTodos }: TodoListCardProps) => { + const { todos } = useTodo(defaultTodos); const { value, handleChange, resetValue } = useInput(); - const { addTodosMutation } = useTodosMutation(); + + const { client, accessCode } = useSocketStore(); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); - addTodosMutation({ content: value, accessCode: accessCode || '' }, { onSuccess: resetValue }); + publishTodoMessage.add(client, accessCode, value); + resetValue(); }; return ( diff --git a/frontend/src/components/PairRoom/TodoListCard/TodoListCard.type.ts b/frontend/src/components/PairRoom/TodoListCard/TodoListCard.type.ts deleted file mode 100644 index 08d86cc8b..000000000 --- a/frontend/src/components/PairRoom/TodoListCard/TodoListCard.type.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Todo { - id: number; - content: string; - isChecked: boolean; - sort: number; -} diff --git a/frontend/src/hooks/PairRoom/usePairRoom.ts b/frontend/src/hooks/PairRoom/usePairRoom.ts index f7acaecc8..4e40f08f2 100644 --- a/frontend/src/hooks/PairRoom/usePairRoom.ts +++ b/frontend/src/hooks/PairRoom/usePairRoom.ts @@ -1,14 +1,12 @@ -import { useEffect, useRef } from 'react'; - -import { Client } from '@stomp/stompjs'; +import { useEffect } from 'react'; +import useSocketStore from '@/stores/socketStore'; import useToastStore from '@/stores/toastStore'; import { getConnection } from '@/apis/websocket/websocket'; const usePairRoom = () => { - const stompClient = useRef(null); - + const { setClient, setIsConnected } = useSocketStore(); const { addToast } = useToastStore(); const handleBeforeUnload = (event: BeforeUnloadEvent) => { @@ -19,11 +17,13 @@ const usePairRoom = () => { const client = getConnection(); client.onConnect = () => { - stompClient.current = client; + setClient(client); + setIsConnected(true); }; client.onDisconnect = () => { - stompClient.current = null; + setClient(null); + setIsConnected(false); }; client.onStompError = (error) => { @@ -37,11 +37,13 @@ const usePairRoom = () => { return () => { client.deactivate(); + setClient(null); + setIsConnected(false); window.removeEventListener('beforeunload', handleBeforeUnload); }; }, []); - return { client: stompClient.current }; + return {}; }; export default usePairRoom; diff --git a/frontend/src/hooks/PairRoom/useTimer.ts b/frontend/src/hooks/PairRoom/useTimer.ts index 2fc055087..1398361e2 100644 --- a/frontend/src/hooks/PairRoom/useTimer.ts +++ b/frontend/src/hooks/PairRoom/useTimer.ts @@ -1,34 +1,33 @@ import { useState, useRef, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Client, Message } from '@stomp/stompjs'; import { useQueryClient } from '@tanstack/react-query'; import { AlarmSound } from '@/assets'; +import useSocketStore from '@/stores/socketStore'; import useToastStore from '@/stores/toastStore'; import { startTimer, stopTimer } from '@/apis/http/timer'; +import { subscribeTopic } from '@/apis/websocket/websocket'; import useNotification from '@/hooks/PairRoom/useNotification'; import { QUERY_KEYS } from '@/constants/queryKeys'; -const STATUS = { - COMPLETE: 'complete', - START: 'start', - RUNNING: 'running', - PAUSE: 'pause', - UPDATE: 'update', -}; +enum TimerStatus { + COMPLETE = 'complete', + START = 'start', + RUNNING = 'running', + PAUSE = 'pause', + UPDATE = 'update', +} + +const STATUS = TimerStatus; + +const useTimer = (defaultTime: number, defaultTimeLeft: number, onTimerStop: () => void) => { + const { client, isConnected, accessCode } = useSocketStore(); -const useTimer = ( - client: Client | null, - accessCode: string, - defaultTime: number, - defaultTimeLeft: number, - onTimerStop: () => void, -) => { const [timeLeft, setTimeLeft] = useState(defaultTimeLeft); const [isActive, setIsActive] = useState(false); @@ -70,7 +69,7 @@ const useTimer = ( setTimeLeft(timeLeft); }; - const handleTimerStatusEvent = (status: string) => { + const handleTimerStatusEvent = (status: TimerStatus) => { switch (status) { case STATUS.COMPLETE: navigate(`/room/${accessCode}/retrospectForm`, { state: { valid: true } }); @@ -99,17 +98,18 @@ const useTimer = ( }; useEffect(() => { - if (client) { - client.subscribe(`/topic/${accessCode}/timer`, (message: Message) => - handleTimerEvent(JSON.parse(message.body).data), - ); - client.subscribe(`/topic/${accessCode}/timer/status`, (message: Message) => - handleTimerStatusEvent(JSON.parse(message.body).data), + if (client && isConnected) { + // 타이머 남은 시간 + subscribeTopic<{ data: number }>(client, `/topic/${accessCode}/timer`, (body) => handleTimerEvent(body.data)); + + // 타이머 상태 + subscribeTopic<{ data: TimerStatus }>(client, `/topic/${accessCode}/timer/status`, (body) => + handleTimerStatusEvent(body.data), ); } return () => { - if (client) { + if (client && isConnected) { client.unsubscribe('/timer'); client.unsubscribe('/timer/status'); } diff --git a/frontend/src/hooks/PairRoom/useTodo.ts b/frontend/src/hooks/PairRoom/useTodo.ts index 8933fc579..00dceb3c2 100644 --- a/frontend/src/hooks/PairRoom/useTodo.ts +++ b/frontend/src/hooks/PairRoom/useTodo.ts @@ -1,18 +1,30 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; -import { Todo } from '@/components/PairRoom/TodoListCard/TodoListCard.type'; +import useSocketStore from '@/stores/socketStore'; -const TODO_MESSAGES = { - GET: 'get', - POST: 'post', - PUT: 'put', - DELETE: 'delete', -}; +import { Todo } from '@/apis/http/todo'; +import { subscribeTopic } from '@/apis/websocket/websocket'; + +const useTodo = (defaultTodos: Todo[]) => { + const [todos, setTodos] = useState(defaultTodos); + + const { client, isConnected, accessCode } = useSocketStore(); + + const handleTodos = (todos: Todo[]) => setTodos(todos); + + useEffect(() => { + if (client && isConnected) { + subscribeTopic(client, `/topic/${accessCode}/todo`, handleTodos); + } -const useTodo = (client: Client | null, accessCode: string) => { - const [todos, setTodos] = useState([]); + return () => { + if (client && isConnected) { + client.unsubscribe(`/topic/${accessCode}/todo`); + } + }; + }, [client]); - return { todos, setTodos }; + return { todos }; }; export default useTodo; diff --git a/frontend/src/pages/PairRoom/PairRoom.tsx b/frontend/src/pages/PairRoom/PairRoom.tsx index 7f2f75751..601ed88c9 100644 --- a/frontend/src/pages/PairRoom/PairRoom.tsx +++ b/frontend/src/pages/PairRoom/PairRoom.tsx @@ -10,6 +10,8 @@ import ReferenceCard from '@/components/PairRoom/ReferenceCard/ReferenceCard'; import TimerCard from '@/components/PairRoom/TimerCard/TimerCard'; import TodoListCard from '@/components/PairRoom/TodoListCard/TodoListCard'; +import useSocketStore from '@/stores/socketStore'; + import useModal from '@/hooks/_common/useModal'; import usePairRoom from '@/hooks/PairRoom/usePairRoom'; @@ -26,6 +28,9 @@ const PairRoom = () => { const [navigator, setNavigator] = useState(''); const [isCardOpen, setIsCardOpen] = useState(false); + usePairRoom(); + const { isConnected } = useSocketStore(); + const { driver: latestDriver, navigator: latestNavigator, @@ -52,9 +57,7 @@ const PairRoom = () => { setNavigator(latestNavigator); }, [latestDriver, latestNavigator]); - const { client } = usePairRoom(accessCode || ''); - - if (isFetching) { + if (isFetching || !isConnected) { return ; } @@ -64,17 +67,14 @@ const PairRoom = () => { updatePairRoleMutation({ accessCode: accessCode || '' })} /> - setIsCardOpen(false)} todos={todos} /> + setIsCardOpen(false)} defaultTodos={todos} /> setIsCardOpen(true)} references={references} diff --git a/frontend/src/pages/PrivateRoutes.tsx b/frontend/src/pages/PrivateRoutes.tsx index 5e06ab575..847de8bd2 100644 --- a/frontend/src/pages/PrivateRoutes.tsx +++ b/frontend/src/pages/PrivateRoutes.tsx @@ -3,6 +3,7 @@ import { Navigate, Outlet, useLocation, useParams } from 'react-router-dom'; import Loading from '@/pages/Loading/Loading'; +import useSocketStore from '@/stores/socketStore'; import useToastStore from '@/stores/toastStore'; import { getPairRoomExists } from '@/apis/http/pairRoom'; @@ -13,6 +14,7 @@ const PrivateRoutes = () => { const [isValid, setIsValid] = useState(null); + const { setAccessCode } = useSocketStore(); const { addToast } = useToastStore(); const validateAccess = async () => { @@ -45,6 +47,7 @@ const PrivateRoutes = () => { useEffect(() => { validateAccess(); + setAccessCode(accessCode || ''); }, []); if (isValid === null) return ; diff --git a/frontend/src/queries/PairRoom/useTimerMutation.ts b/frontend/src/queries/PairRoom/useTimerMutation.ts index d1840c5b7..614d2f268 100644 --- a/frontend/src/queries/PairRoom/useTimerMutation.ts +++ b/frontend/src/queries/PairRoom/useTimerMutation.ts @@ -2,7 +2,7 @@ import { useMutation } from '@tanstack/react-query'; import useToastStore from '@/stores/toastStore'; -import { updateDuration } from '@/apis/timer'; +import { updateDuration } from '@/apis/http/timer'; const useTimerMutation = () => { const { addToast } = useToastStore(); diff --git a/frontend/src/stores/socketStore.ts b/frontend/src/stores/socketStore.ts new file mode 100644 index 000000000..930b77bc3 --- /dev/null +++ b/frontend/src/stores/socketStore.ts @@ -0,0 +1,22 @@ +import { Client } from '@stomp/stompjs'; +import { create } from 'zustand'; + +interface SocketStore { + client: Client | null; + isConnected: boolean; + accessCode: string; + setClient: (client: Client | null) => void; + setIsConnected: (isConnected: boolean) => void; + setAccessCode: (accessCode: string) => void; +} + +const useSocketStore = create((set) => ({ + client: null, + isConnected: false, + accessCode: '', + setClient: (client) => set({ client }), + setAccessCode: (accessCode) => set({ accessCode }), + setIsConnected: (isConnected) => set({ isConnected }), +})); + +export default useSocketStore; From 67e1bab2a2cf1b70c9db514654451daf6a1c79b8 Mon Sep 17 00:00:00 2001 From: greetings1012 Date: Wed, 22 Jan 2025 16:41:39 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Publish=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=ED=94=84=EB=A6=AC?= =?UTF-8?q?=ED=94=BD=EC=8A=A4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/apis/websocket/todo.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/apis/websocket/todo.ts b/frontend/src/apis/websocket/todo.ts index ea6d142ce..80cfd3b7b 100644 --- a/frontend/src/apis/websocket/todo.ts +++ b/frontend/src/apis/websocket/todo.ts @@ -4,18 +4,18 @@ import { publishMessage } from '@/apis/websocket/websocket'; export const publishTodoMessage = { add: (client: Client | null, accessCode: string, contents: string) => { - publishMessage(client, `/topic/${accessCode}/todo/add`, { contents }); + publishMessage(client, `/send/${accessCode}/todo/add`, { contents }); }, updateContents: (client: Client | null, accessCode: string, todoId: number, contents: string) => { - publishMessage(client, `/topic/${accessCode}/todo/update/${todoId}/contents`, { contents }); + publishMessage(client, `/send/${accessCode}/todo/update/${todoId}/contents`, { contents }); }, updateOrder: (client: Client | null, accessCode: string, todoId: number, order: number) => { - publishMessage(client, `/topic/${accessCode}/todo/update/${todoId}/order`, { order }); + publishMessage(client, `/send/${accessCode}/todo/update/${todoId}/order`, { order }); }, updateChecked: (client: Client | null, accessCode: string, todoId: number) => { - publishMessage(client, `/topic/${accessCode}/todo/update/${todoId}/checked`); + publishMessage(client, `/send/${accessCode}/todo/update/${todoId}/checked`); }, delete: (client: Client | null, accessCode: string, todoId: number) => { - publishMessage(client, `/topic/${accessCode}/todo/delete/${todoId}`); + publishMessage(client, `/send/${accessCode}/todo/delete/${todoId}`); }, };