diff --git a/package.json b/package.json index c770b90..231dd09 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@hookform/resolvers": "^3.9.1", "@tanstack/react-query": "4", "axios": "^1.7.9", + "jwt-decode": "^4.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", @@ -26,6 +27,7 @@ "@types/node": "^22.10.2", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "@types/socket.io-client": "^3.0.0", "@vitejs/plugin-react": "^4.3.4", "eslint": "^9.15.0", "eslint-plugin-react-hooks": "^5.0.0", diff --git a/src/api/axios.ts b/src/api/axios.ts index e308c78..344ab59 100644 --- a/src/api/axios.ts +++ b/src/api/axios.ts @@ -6,6 +6,7 @@ export const instance = axios.create({ export const authInstance = axios.create({ baseURL: import.meta.env.VITE_BASE_URL, + withCredentials: true, }); authInstance.interceptors.request.use( diff --git a/src/api/gameRoomApi.ts b/src/api/gameRoomApi.ts index ba197a4..6320084 100644 --- a/src/api/gameRoomApi.ts +++ b/src/api/gameRoomApi.ts @@ -34,6 +34,20 @@ export const getGameRoomsList = async () => { } }; +export const getGameRoomInfo = async (roomId: number) => { + try { + const response = await authInstance.get(`/gameRoom/${roomId}`); + + return response.data; + } catch (error) { + const axiosError = error as AxiosError; + + if (axiosError.response) { + return axiosError.response.data; + } + } +}; + export const createGameRoom = async ({ roomName, }: { @@ -43,6 +57,9 @@ export const createGameRoom = async ({ const response = await authInstance.post( "gameRoom/create", { roomName }, + { + headers: { Authorization: localStorage.getItem("accessToken") }, + }, ); return response.data; @@ -64,7 +81,7 @@ export const createGameRoom = async ({ export const joinGameRoom = async (gameRoomId: number) => { try { const response = await authInstance.post(`/gameRoom/join/${gameRoomId}`); - console.log(response, "joinresponse확인"); + if (response.status === 200) { return response.data; } diff --git a/src/api/userAuthApi.ts b/src/api/userAuthApi.ts index eeb1335..98def6c 100644 --- a/src/api/userAuthApi.ts +++ b/src/api/userAuthApi.ts @@ -54,16 +54,14 @@ export const loginUser = async (logInfo: { export const refreshToken = async () => { try { - const response = await authInstance.post("/auth/refresh", { - withCredentials: true, - }); + const response = await authInstance.post("/auth/refresh"); console.log(response); - const accessToken = response?.headers.authorization; + const { accessToken } = response.data; localStorage.setItem("accessToken", accessToken); return response.data.accessToken; - } catch (err) { - console.log(err); + } catch (error) { + console.log(error); } }; diff --git a/src/components/ui/card/cardList/GameRoomCard.tsx b/src/components/ui/card/cardList/GameRoomCard.tsx index b36228f..7db04fb 100644 --- a/src/components/ui/card/cardList/GameRoomCard.tsx +++ b/src/components/ui/card/cardList/GameRoomCard.tsx @@ -1,14 +1,18 @@ -import { useJoinRoomMutation } from "../../../../hooks/useMutation"; +import { useJoinGameRoomMutation } from "../../../../hooks/useMutation"; +import { useRoomIdStore } from "../../../../store/useRoomIdStore"; import Badge from "../../badge/Badge"; import Button from "../../button/Button"; import * as S from "./GameRoomCardStyle"; export default function GameRoomCard({ room }: { room: IRoomProps }) { + const { setRoomId } = useRoomIdStore(); const disabled = room.currentCount === 2 ? true : false; - const mutation = useJoinRoomMutation(); + const mutation = useJoinGameRoomMutation(); const onClickJoin = (roomId: number) => { + setRoomId(roomId); mutation.mutate(roomId); }; + return ( diff --git a/src/components/ui/card/profileCard/ProfileCard.tsx b/src/components/ui/card/profileCard/ProfileCard.tsx index 2dfcf47..28cffdb 100644 --- a/src/components/ui/card/profileCard/ProfileCard.tsx +++ b/src/components/ui/card/profileCard/ProfileCard.tsx @@ -1,11 +1,19 @@ import { ReactNode } from "react"; -import { CardIndex, PlayerCard } from "./ProfileCardStyle"; +import PlayerCard from "./playerCard/playerCard"; +import * as S from "./ProfileCardStyle"; -export default function ProfileCard({ children }: { children: ReactNode }) { +interface IProfileCard { + nickname: string | undefined; + children: ReactNode; +} + +export default function ProfileCard({ nickname, children }: IProfileCard) { return (
- 나 or 상대플레이어 - {children} + {children} + + +
); } diff --git a/src/components/ui/card/profileCard/ProfileCardStyle.ts b/src/components/ui/card/profileCard/ProfileCardStyle.ts index 70b7458..54bbd2c 100644 --- a/src/components/ui/card/profileCard/ProfileCardStyle.ts +++ b/src/components/ui/card/profileCard/ProfileCardStyle.ts @@ -14,7 +14,7 @@ export const CardIndex = styled.div` line-height: 46px; `; -export const PlayerCard = styled.div` +export const PlayerCardContainer = styled.div` display: flex; flex-direction: column; gap: 22px; diff --git a/src/components/ui/card/profileCard/playerCard/playerCard.tsx b/src/components/ui/card/profileCard/playerCard/playerCard.tsx index ce8c272..0be5335 100644 --- a/src/components/ui/card/profileCard/playerCard/playerCard.tsx +++ b/src/components/ui/card/profileCard/playerCard/playerCard.tsx @@ -1,14 +1,18 @@ +import { Fragment } from "react/jsx-runtime"; import Button from "../../../button/Button"; import Profile from "../../../profile/Profile"; -import ProfileCard from "../ProfileCard"; -export default function PlayerCard() { +export default function PlayerCard({ + nickname, +}: { + nickname: string | undefined; +}) { return ( - - + + - + ); } diff --git a/src/components/ui/profile/Profile.tsx b/src/components/ui/profile/Profile.tsx index 8da43ae..9f5c800 100644 --- a/src/components/ui/profile/Profile.tsx +++ b/src/components/ui/profile/Profile.tsx @@ -1,15 +1,11 @@ import * as S from "./profileStyle"; -export default function Profile({ nickname, rank, avatar }: IprofileProps) { +export default function Profile({ nickname }: IprofileProps) { return ( - {avatar} + {nickname} - - - {rank} - ); diff --git a/src/components/ui/profile/profile.props.d.ts b/src/components/ui/profile/profile.props.d.ts index d29b110..aa94df6 100644 --- a/src/components/ui/profile/profile.props.d.ts +++ b/src/components/ui/profile/profile.props.d.ts @@ -1,5 +1,5 @@ interface IprofileProps { - nickname: string; - rank: string | number; + nickname: string | undefined; + rank?: string | number; avatar?: string; } diff --git a/src/hooks/useMutation.tsx b/src/hooks/useMutation.tsx index f3b0793..9e7182c 100644 --- a/src/hooks/useMutation.tsx +++ b/src/hooks/useMutation.tsx @@ -32,7 +32,7 @@ export const useCreateRoomMutation = () => { }); }; -export const useJoinRoomMutation = () => { +export const useJoinGameRoomMutation = () => { const navigate = useNavigate(); return useMutation({ mutationFn: joinGameRoom, diff --git a/src/hooks/useQuery.tsx b/src/hooks/useQuery.tsx index 3a5e6bd..629fd42 100644 --- a/src/hooks/useQuery.tsx +++ b/src/hooks/useQuery.tsx @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { getGameRoomsList } from "../api/gameRoomApi"; +import { getGameRoomInfo, getGameRoomsList } from "../api/gameRoomApi"; export const useGetGameRoomList = () => { return useQuery({ @@ -7,3 +7,10 @@ export const useGetGameRoomList = () => { queryFn: getGameRoomsList, }); }; + +export const useGetGameRoomInfo = (roomId: number) => { + return useQuery({ + queryKey: ["gameRoomInfo", roomId], + queryFn: () => getGameRoomInfo(roomId), + }); +}; diff --git a/src/pages/game/GameRoom.tsx b/src/pages/game/GameRoom.tsx index 3787dea..f18db9c 100644 --- a/src/pages/game/GameRoom.tsx +++ b/src/pages/game/GameRoom.tsx @@ -1,12 +1,100 @@ +import { io } from "socket.io-client"; import ChattingBox from "../../components/ui/card/chatting/Chatting"; -// import ProfileCard from "../../components/ui/card/profileCard/ProfileCard"; +import { useSocketStore } from "../../store/useSocketStore"; +import ProfileCard from "../../components/ui/card/profileCard/ProfileCard"; import * as S from "./gameRoomStyle"; +import { useEffect, useState } from "react"; +import { jwtDecode } from "jwt-decode"; +import { useGetGameRoomInfo } from "../../hooks/useQuery"; +import { useParams } from "react-router-dom"; + +interface IUser { + id: number; + roomId: number; + userId: number; + joinedAt: string; + userNickname: string; +} + +interface IJoinRoomResponse { + sender: string; + userNickname: string; + message: string; +} + +interface IJwtDecode { + exp: number; + iat: number; + userEmail: string; + userId: number; +} export default function GameRoom() { + const { socket, setSocket } = useSocketStore(); + const params = useParams(); + const roomId = Number(params.id); + const [userMyId, setUserMyId] = useState(undefined); + const [myInfo, setMyInfo] = useState(undefined); + const [otherInfo, setOtherInfo] = useState(undefined); + const accessToken = localStorage.getItem("accessToken"); + const { data, refetch } = useGetGameRoomInfo(roomId); + + useEffect(() => { + if (!accessToken) { + console.error("Access token is missing."); + //오류 표시하거나 로그인창으로 리다이렉션 하기 + return; + } + + const decoded = jwtDecode(accessToken); + setUserMyId(decoded.userId); + + if (socket) { + socket.on("connect", () => { + console.log(`Socket connected: ${socket.id}`); + }); + } + + const newSocket = io(`wss://api.davincicodegame.store/game`, { + extraHeaders: { Authorization: accessToken }, + auth: { token: accessToken }, + }); + setSocket(newSocket); + + newSocket.emit("joinRoom", { roomId }, (response: IJoinRoomResponse) => { + console.log(`Join room response: ${JSON.stringify(response)}`); + }); + }, []); + + useEffect(() => { + const myProfile = data?.users.find( + (user: IUser) => user.userId == userMyId, + ); + + setMyInfo(myProfile); + + const otherProfile = data?.users.filter( + (user: IUser) => user.userId !== userMyId, + ); + + setOtherInfo(otherProfile); + }, [data]); + + useEffect(() => { + if (!socket) return; + socket.on("join", async (data) => { + console.log(`${JSON.stringify(data)}`); + const refetchInfo = await refetch(); + console.log(refetchInfo, "리페치정보"); + }); + }, [socket]); + return ( - {/* */} + + 상대 플레이어 + @@ -16,7 +104,7 @@ export default function GameRoom() { - {/* */} + ); diff --git a/src/pages/game/gameRoomStyle.ts b/src/pages/game/gameRoomStyle.ts index 781df4e..c647da9 100644 --- a/src/pages/game/gameRoomStyle.ts +++ b/src/pages/game/gameRoomStyle.ts @@ -9,18 +9,16 @@ export const container = styled.div` `; export const leftContainer = styled.div` - padding-top: 44px; - margin-left: 63px; width: 390px; - height: 100%; + height: 1000px; display: flex; flex-direction: column; + justify-content: space-between; background-color: yellow; - gap: 90px; `; export const centerContainer = styled.div` - height: 100%; + height: 1000px; width: 100%; background-color: green; position: relative; @@ -30,7 +28,7 @@ export const rightContainer = styled.div` margin-right: 77px; padding-bottom: 44px; width: 390px; - height: 100%; + height: 1000px; display: flex; gap: 40px; flex-direction: column; diff --git a/src/pages/game/waitingPage/WaitingPage.tsx b/src/pages/game/waitingPage/WaitingPage.tsx deleted file mode 100644 index 456a1cb..0000000 --- a/src/pages/game/waitingPage/WaitingPage.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import ProfileCard from "../../../components/ui/card/profileCard/ProfileCard"; -import { ReactNode } from "react"; -import ChattingBox from "../../../components/ui/card/chatting/Chatting"; -import { ChattingReadyDiv, PageBox } from "./waitingPageStyle"; - -export default function WaitingPage({ children }: { children: ReactNode }) { - return ( - - {children} - - - {children} - - - ); -} diff --git a/src/pages/game/waitingPage/waitingPageStyle.ts b/src/pages/game/waitingPage/waitingPageStyle.ts deleted file mode 100644 index 25f28d7..0000000 --- a/src/pages/game/waitingPage/waitingPageStyle.ts +++ /dev/null @@ -1,14 +0,0 @@ -import styled from "styled-components"; - -export const PageBox = styled.div` - width: 100%; - height: 100%; - padding: 43px 63px 77px 44px; -`; - -export const ChattingReadyDiv = styled.div` - display: flex; - justify-content: space-between; - align-items: end; - margin-top: 90px; -`; diff --git a/src/routes/router.tsx b/src/routes/router.tsx index 460b4c6..6ac6e50 100644 --- a/src/routes/router.tsx +++ b/src/routes/router.tsx @@ -8,7 +8,6 @@ import CheckPassword from "@pages/mypage/checkPassword/CheckPassword"; import UpdateMyPage from "@pages/mypage/updateMypage/UpdateMyPage"; import GameRoom from "@pages/game/GameRoom"; import Game from "@pages/game/Game"; -import TestPage from "../pages/testpage/Testpage"; import Home from "@pages/home/Home"; export const router = createBrowserRouter([ @@ -58,10 +57,6 @@ export const router = createBrowserRouter([ path: "/user/mypage/updateMyPage", element: , }, - { - path: "/test", - element: , - }, ], }, ]); diff --git a/src/store/useRoomIdStore.ts b/src/store/useRoomIdStore.ts new file mode 100644 index 0000000..e636d93 --- /dev/null +++ b/src/store/useRoomIdStore.ts @@ -0,0 +1,11 @@ +import { create } from "zustand"; + +interface IRoomState { + roomId: number; + setRoomId: (id: number) => void; +} + +export const useRoomIdStore = create((set) => ({ + roomId: 0, + setRoomId: (id: number) => set({ roomId: id }), +})); diff --git a/src/store/useSocketStore.ts b/src/store/useSocketStore.ts new file mode 100644 index 0000000..57df14a --- /dev/null +++ b/src/store/useSocketStore.ts @@ -0,0 +1,14 @@ +// useSocketStore.ts +import { create } from "zustand"; +import { Socket } from "socket.io-client"; + +interface SocketState { + socket: Socket | null; + setSocket: (newSocket: Socket | null) => void; +} + +export const useSocketStore = create((set) => ({ + socket: null, + + setSocket: (newSocket: Socket | null) => set({ socket: newSocket }), +})); diff --git a/yarn.lock b/yarn.lock index ac21f52..defda91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -546,6 +546,11 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz#c82f04a09ba481e13857d6f2516e072aaa51b7f4" integrity sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg== +"@socket.io/component-emitter@~3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== + "@tanstack/query-core@4.36.1": version "4.36.1" resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.36.1.tgz#79f8c1a539d47c83104210be2388813a7af2e524" @@ -632,6 +637,13 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/socket.io-client@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-3.0.0.tgz#d0b8ea22121b7c1df68b6a923002f9c8e3cefb42" + integrity sha512-s+IPvFoEIjKA3RdJz/Z2dGR4gLgysKi8owcnrVwNjgvc01Lk68LJDDsG2GRqegFITcxmvCMYM7bhMpwEMlHmDg== + dependencies: + socket.io-client "*" + "@types/stylis@4.2.5": version "4.2.5" resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.5.tgz#1daa6456f40959d06157698a653a9ab0a70281df" @@ -904,6 +916,13 @@ debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "^2.1.3" +debug@~4.3.1, debug@~4.3.2: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -919,6 +938,22 @@ electron-to-chromium@^1.5.73: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz#db20295c5061b68f07c8ea4dfcbd701485d94a3d" integrity sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ== +engine.io-client@~6.6.1: + version "6.6.2" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.6.2.tgz#e0a09e1c90effe5d6264da1c56d7281998f1e50b" + integrity sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + xmlhttprequest-ssl "~2.1.1" + +engine.io-parser@~5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" + integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== + esbuild@^0.24.2: version "0.24.2" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d" @@ -1269,6 +1304,11 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jwt-decode@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" + integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== + keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -1591,6 +1631,24 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +socket.io-client@*: + version "4.8.1" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb" + integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.6.1" + socket.io-parser "~4.2.4" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -1719,6 +1777,16 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + +xmlhttprequest-ssl@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23" + integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"