diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx index a0766efaf..3ce883806 100644 --- a/frontend/.storybook/preview.tsx +++ b/frontend/.storybook/preview.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type { Preview } from '@storybook/react'; +import { RecoilRoot } from 'recoil'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; import { Global, ThemeProvider } from '@emotion/react'; @@ -32,12 +33,14 @@ const preview: Preview = { decorators: [ (Story) => ( - - - - - - + + + + + + + + ), ], diff --git a/frontend/src/components/SelectContainer/SelectContainer.hook.ts b/frontend/src/components/SelectContainer/SelectContainer.hook.ts index 34416b9c6..f670ed445 100644 --- a/frontend/src/components/SelectContainer/SelectContainer.hook.ts +++ b/frontend/src/components/SelectContainer/SelectContainer.hook.ts @@ -3,9 +3,9 @@ import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { fetchRoundVoteIsFinished } from '@/apis/balanceContent'; +import { POLLING_DELAY } from '@/constants/config'; import { QUERY_KEYS } from '@/constants/queryKeys'; import { ROUTES } from '@/constants/routes'; -import { POLLING_DELAY } from '@/constants/time'; interface UseRoundIsFinishedQueryProps { contentId?: number; diff --git a/frontend/src/components/Timer/Timer.hook.ts b/frontend/src/components/Timer/Timer.hook.ts index 767be261e..8f20278a1 100644 --- a/frontend/src/components/Timer/Timer.hook.ts +++ b/frontend/src/components/Timer/Timer.hook.ts @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { convertMsecToSecond } from './Timer.util'; -import { ONE_SECOND } from '@/constants/time'; +import { POLLING_DELAY } from '@/constants/config'; import useBalanceContentQuery from '@/hooks/useBalanceContentQuery'; const INITIAL_WIDTH = 100; @@ -33,7 +33,7 @@ const useRoundTimer = (roomId: number) => { timeout.current = setInterval(() => { setLeftRoundTime((prev) => prev - 1); setBarWidthPercent((prevWidth) => (prevWidth > 0 ? prevWidth - DECREASE_RATE : 0)); - }, ONE_SECOND); + }, POLLING_DELAY); return () => { clearInterval(timeout.current); diff --git a/frontend/src/components/Timer/Timer.util.ts b/frontend/src/components/Timer/Timer.util.ts index d4cd88a57..6d6f1727c 100644 --- a/frontend/src/components/Timer/Timer.util.ts +++ b/frontend/src/components/Timer/Timer.util.ts @@ -1,4 +1,4 @@ -import { ONE_SECOND } from '@/constants/time'; +import { POLLING_DELAY } from '@/constants/config'; export const formatLeftRoundTime = (leftRoundTime: number) => { const minutes = Math.floor(leftRoundTime / 60); @@ -11,6 +11,6 @@ export const formatLeftRoundTime = (leftRoundTime: number) => { }; export const convertMsecToSecond = (msec: number) => { - const UNIT_MSEC = ONE_SECOND; + const UNIT_MSEC = POLLING_DELAY; return msec / UNIT_MSEC; }; diff --git a/frontend/src/components/common/RoomSettingModal/RoomSettingModal.test.tsx b/frontend/src/components/common/RoomSettingModal/RoomSettingModal.test.tsx index 2ff0a1ce4..c656a5df0 100644 --- a/frontend/src/components/common/RoomSettingModal/RoomSettingModal.test.tsx +++ b/frontend/src/components/common/RoomSettingModal/RoomSettingModal.test.tsx @@ -3,7 +3,7 @@ import { userEvent } from '@testing-library/user-event'; import RoomSettingModal from './RoomSettingModal'; -import { ONE_SECOND } from '@/constants/time'; +import { POLLING_DELAY } from '@/constants/config'; import ROOM_INFO from '@/mocks/data/roomInfo.json'; import { useGetRoomInfo } from '@/pages/ReadyPage/useGetRoomInfo'; import { customRender, wrapper } from '@/utils/test-utils'; @@ -88,7 +88,7 @@ describe('RoomSettingModal 방 설정 모달 테스트', () => { expect(result.current.roomSetting).toEqual(ROOM_INFO.roomSetting); }); - await clickButton(`${TIME_LIMIT / ONE_SECOND}초`); + await clickButton(`${TIME_LIMIT / POLLING_DELAY}초`); await clickButton('적용'); await waitFor(() => { diff --git a/frontend/src/components/common/RoomSettingModal/RoomSettingModal.tsx b/frontend/src/components/common/RoomSettingModal/RoomSettingModal.tsx index 007b68490..300fa7208 100644 --- a/frontend/src/components/common/RoomSettingModal/RoomSettingModal.tsx +++ b/frontend/src/components/common/RoomSettingModal/RoomSettingModal.tsx @@ -9,7 +9,7 @@ import { } from './RoomSettingModal.styled'; import Modal from '../Modal/Modal'; -import { ONE_SECOND } from '@/constants/time'; +import { POLLING_DELAY } from '@/constants/config'; const TOTAL_ROUND_LIST = [5, 7, 10]; const TIMER_PER_ROUND_LIST = [5000, 10000, 15000]; @@ -58,7 +58,7 @@ const RoomSettingModal = ({ isOpen, onClose }: RoomSettingModalProps) => { onClick={handleClickTimeLimit} value={timeLimit} > - {timeLimit / ONE_SECOND}초 + {timeLimit / POLLING_DELAY}초 ))} diff --git a/frontend/src/constants/config.ts b/frontend/src/constants/config.ts new file mode 100644 index 000000000..af490ba5f --- /dev/null +++ b/frontend/src/constants/config.ts @@ -0,0 +1,2 @@ +export const NICKNAME_MAX_LENGTH = 12; +export const POLLING_DELAY = 1000; diff --git a/frontend/src/constants/time.ts b/frontend/src/constants/time.ts deleted file mode 100644 index 379bf3026..000000000 --- a/frontend/src/constants/time.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const ONE_SECOND = 1000; - -export const POLLING_DELAY = ONE_SECOND; diff --git a/frontend/src/hooks/useMyGameStatusQuery.ts b/frontend/src/hooks/useMyGameStatusQuery.ts index c9e78de48..7f3b6dac1 100644 --- a/frontend/src/hooks/useMyGameStatusQuery.ts +++ b/frontend/src/hooks/useMyGameStatusQuery.ts @@ -1,8 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { checkMyGameStatus } from '@/apis/balanceContent'; +import { POLLING_DELAY } from '@/constants/config'; import { QUERY_KEYS } from '@/constants/queryKeys'; -import { ONE_SECOND } from '@/constants/time'; interface useMyGameStatusQueryProps { currentRound: number | undefined; @@ -22,7 +22,7 @@ const useMyGameStatusQuery = ({ roomId, currentRound }: useMyGameStatusQueryProp }); }, enabled: !!currentRound, - refetchInterval: ONE_SECOND, + refetchInterval: POLLING_DELAY, gcTime: 0, }); diff --git a/frontend/src/mocks/data/roomInfo.json b/frontend/src/mocks/data/roomInfo.json index 3f04cabc0..802d1180f 100644 --- a/frontend/src/mocks/data/roomInfo.json +++ b/frontend/src/mocks/data/roomInfo.json @@ -34,5 +34,9 @@ "nickname": "프콩", "isMaster": false } - ] + ], + "master": { + "memberId": 2, + "nickname": "든콩" + } } diff --git a/frontend/src/mocks/handlers/balanceContentHandler.ts b/frontend/src/mocks/handlers/balanceContentHandler.ts index 9c49c39f2..641d4acfe 100644 --- a/frontend/src/mocks/handlers/balanceContentHandler.ts +++ b/frontend/src/mocks/handlers/balanceContentHandler.ts @@ -4,7 +4,7 @@ import BALANCE_CONTENT from '../data/balanceContent.json'; import MY_GAME_STATUS from '../data/myGameStatus.json'; import ROUND_VOTE_IS_FINISHED from '../data/roundVoteIsFinished.json'; -import { ONE_SECOND } from '@/constants/time'; +import { POLLING_DELAY } from '@/constants/config'; import { MOCK_API_URL } from '@/constants/url'; import { BalanceContent } from '@/types/balanceContent'; @@ -15,10 +15,10 @@ const fetchBalanceContentHandler = () => { const fetchIsFinishedHandler = () => { setTimeout(() => { ROUND_VOTE_IS_FINISHED.isFinished = true; - }, 10 * ONE_SECOND); + }, 10 * POLLING_DELAY); setTimeout(() => { ROUND_VOTE_IS_FINISHED.isFinished = false; - }, 12 * ONE_SECOND); + }, 12 * POLLING_DELAY); return HttpResponse.json(ROUND_VOTE_IS_FINISHED); }; diff --git a/frontend/src/pages/GameResultPage/hooks/useCheckRoomReset.ts b/frontend/src/pages/GameResultPage/hooks/useCheckRoomReset.ts index 87f94e01c..0c9762398 100644 --- a/frontend/src/pages/GameResultPage/hooks/useCheckRoomReset.ts +++ b/frontend/src/pages/GameResultPage/hooks/useCheckRoomReset.ts @@ -3,9 +3,9 @@ import { useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { isRoomInitial } from '@/apis/room'; +import { POLLING_DELAY } from '@/constants/config'; import { QUERY_KEYS } from '@/constants/queryKeys'; import { ROUTES } from '@/constants/routes'; -import { ONE_SECOND } from '@/constants/time'; export const useIsRoomInitial = () => { const { roomId } = useParams(); @@ -13,7 +13,7 @@ export const useIsRoomInitial = () => { const { data } = useQuery({ queryKey: [QUERY_KEYS.isRoomInitial, Number(roomId)], queryFn: async () => await isRoomInitial(Number(roomId)), - refetchInterval: ONE_SECOND, + refetchInterval: POLLING_DELAY, gcTime: 0, }); diff --git a/frontend/src/pages/NicknamePage/NicknameInput/NicknameInput.styled.ts b/frontend/src/pages/NicknamePage/NicknameInput/NicknameInput.styled.ts new file mode 100644 index 000000000..f5055c113 --- /dev/null +++ b/frontend/src/pages/NicknamePage/NicknameInput/NicknameInput.styled.ts @@ -0,0 +1,26 @@ +import { css } from '@emotion/react'; + +import { Theme } from '@/styles/Theme'; + +export const nicknameInputContainer = css` + display: flex; + align-items: center; + width: 100%; + height: 4.8rem; + padding: 0 1rem; + border-radius: ${Theme.borderRadius.radius10}; + + background-color: ${Theme.color.gray200}; +`; + +export const nicknameInput = css` + width: 100%; + border: 0; + + background-color: ${Theme.color.gray200}; + outline: none; +`; + +export const nicknameLengthText = css` + color: ${Theme.color.gray500}; +`; diff --git a/frontend/src/pages/NicknamePage/NicknameInput/NicknameInput.tsx b/frontend/src/pages/NicknamePage/NicknameInput/NicknameInput.tsx new file mode 100644 index 000000000..27b7693db --- /dev/null +++ b/frontend/src/pages/NicknamePage/NicknameInput/NicknameInput.tsx @@ -0,0 +1,38 @@ +import { RefObject } from 'react'; + +import useNicknameInput from './hooks/useNicknameInput'; +import { nicknameInput, nicknameInputContainer, nicknameLengthText } from './NicknameInput.styled'; +import createRandomNickname from '../createRandomNickname'; + +import { NICKNAME_MAX_LENGTH } from '@/constants/config'; + +interface NicknameInputProps { + nicknameInputRef: RefObject; + handleMakeOrEnterRoom: () => void; +} + +const NicknameInput = ({ nicknameInputRef, handleMakeOrEnterRoom }: NicknameInputProps) => { + const { nickname, handleChangeInput, handleKeyDown } = useNicknameInput({ + handleMakeOrEnterRoom, + }); + const randomNickname = createRandomNickname(); + + return ( +
+ + + {nickname.length}/{NICKNAME_MAX_LENGTH} + +
+ ); +}; + +export default NicknameInput; diff --git a/frontend/src/pages/NicknamePage/NicknameInput/hooks/useNicknameInput.ts b/frontend/src/pages/NicknamePage/NicknameInput/hooks/useNicknameInput.ts new file mode 100644 index 000000000..587a73092 --- /dev/null +++ b/frontend/src/pages/NicknamePage/NicknameInput/hooks/useNicknameInput.ts @@ -0,0 +1,27 @@ +import { ChangeEvent, useState } from 'react'; + +import { NICKNAME_MAX_LENGTH } from '@/constants/config'; + +interface UseNicknameInputProps { + handleMakeOrEnterRoom: () => void; +} + +const useNicknameInput = ({ handleMakeOrEnterRoom }: UseNicknameInputProps) => { + const [nickname, setNickname] = useState(''); + + const handleChangeInput = (e: ChangeEvent) => { + if (e.target.value.length <= NICKNAME_MAX_LENGTH) { + setNickname(e.target.value); + } + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleMakeOrEnterRoom(); + } + }; + + return { nickname, handleChangeInput, handleKeyDown }; +}; + +export default useNicknameInput; diff --git a/frontend/src/pages/NicknamePage/NicknamePage.styled.ts b/frontend/src/pages/NicknamePage/NicknamePage.styled.ts index ba430df72..073c91515 100644 --- a/frontend/src/pages/NicknamePage/NicknamePage.styled.ts +++ b/frontend/src/pages/NicknamePage/NicknamePage.styled.ts @@ -18,10 +18,15 @@ export const profileImg = css` width: 60%; `; -export const nicknameBox = css` +export const nicknameContainer = css` + display: flex; + flex-direction: column; + gap: 1.6rem; width: 26.8rem; margin: 2rem 0; +`; +export const nicknameTitle = css` font-weight: 600; font-size: 1.6rem; `; @@ -44,6 +49,11 @@ export const nicknameInput = css` background-color: ${Theme.color.gray200}; outline: none; `; + +export const nicknameLengthText = css` + color: ${Theme.color.gray500}; +`; + export const noVoteTextContainer = css` display: flex; flex-direction: column; @@ -61,4 +71,4 @@ export const noVoteText = css` export const angryImage = css` width: 16rem; height: 14rem; -`; \ No newline at end of file +`; diff --git a/frontend/src/pages/NicknamePage/NicknamePage.tsx b/frontend/src/pages/NicknamePage/NicknamePage.tsx index 8c2dd70c4..98fe45c7d 100644 --- a/frontend/src/pages/NicknamePage/NicknamePage.tsx +++ b/frontend/src/pages/NicknamePage/NicknamePage.tsx @@ -1,20 +1,19 @@ - import { useQuery } from '@tanstack/react-query'; import { useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import NicknameInput from './NicknameInput/NicknameInput'; import { - nicknameBox, - nicknameInputWrapper, - nicknameInput, profileWrapper, profileImg, noVoteTextContainer, noVoteText, angryImage, + nicknameTitle, + nicknameContainer, } from './NicknamePage.styled'; -import { useMakeOrEnterRoom } from './useMakeOrEnterRoom'; +import useMakeOrEnterRoom from './useMakeOrEnterRoom'; import { isJoinableRoom } from '@/apis/room'; import AngryDdangkong from '@/assets/images/angryDdangkong.png'; @@ -27,15 +26,15 @@ import { memberInfoState, roomUuidState } from '@/recoil/atom'; const NicknamePage = () => { const { isOpen, show, close } = useModal(); - const { randomNickname, nicknameInputRef, handleMakeOrEnterRoom, isLoading } = - useMakeOrEnterRoom(show); + const { nicknameInputRef, handleMakeOrEnterRoom, isLoading } = useMakeOrEnterRoom(show); const { isMaster } = useRecoilValue(memberInfoState); const { roomUuid } = useParams(); - const [, setRoomUuidState] = useRecoilState(roomUuidState); + const setRoomUuidState = useSetRecoilState(roomUuidState); - const { data } = useQuery({ + const { data, isLoading: isJoinableLoading } = useQuery({ queryKey: ['isJoinable', roomUuid], queryFn: async () => isJoinableRoom(roomUuid || ''), + enabled: !!roomUuid, }); useEffect(() => { @@ -44,7 +43,7 @@ const NicknamePage = () => { } }, [roomUuid, setRoomUuidState]); - if (roomUuid && !data?.isJoinable) + if (!isJoinableLoading && roomUuid && !data?.isJoinable) return (
화난 땅콩 @@ -57,25 +56,23 @@ const NicknamePage = () => {
사용자 프로필
-
닉네임
-
- + 닉네임 +