diff --git a/package-lock.json b/package-lock.json index e3a1a2f9..34c0a9a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.18.0", + "recoil": "^0.7.7", + "socket.io-client": "^4.7.2", "styled-components": "^6.1.0" }, "devDependencies": { @@ -2031,6 +2033,11 @@ "node": ">=14.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@swc/core": { "version": "1.3.96", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.96.tgz", @@ -2831,7 +2838,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2887,6 +2893,26 @@ "node": ">=6.0.0" } }, + "node_modules/engine.io-client": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", + "integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3429,6 +3455,11 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3747,8 +3778,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -4185,6 +4215,25 @@ } } }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", @@ -4336,6 +4385,32 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -4652,6 +4727,34 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 06619bbc..80109d87 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.18.0", + "recoil": "^0.7.7", + "socket.io-client": "^4.7.2", "styled-components": "^6.1.0" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index a6d5dd6d..7b9b6a70 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,14 +2,17 @@ import { BrowserRouter } from 'react-router-dom'; import { ChakraProvider } from '@chakra-ui/react'; import MainRouter from './router/MainRouter'; import LoginRouter from './router/LoginRouter'; +import { RecoilRoot } from 'recoil'; function App() { return ( - - - - + + + + + + ); } diff --git a/src/api/index.ts b/src/api/index.ts index c932f7fa..51fd4e03 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,6 +2,7 @@ import axios from 'axios'; import { CONTENT_TYPE, SERVER_ID, SERVER_URL } from '../constant'; import { JoinData } from '../interfaces/interface'; + const client = axios.create({ baseURL: SERVER_URL, headers: { @@ -30,4 +31,61 @@ export const getAllUsers = async (accessToken: string) => { }, }); return res; + +export const createGameRooms = async ( + accessToken: string, + name: string, + users: string[], + isPrivate: boolean, +) => { + const authData = { + name, + users, + isPrivate, + }; + try { + const response = await axios.post(`${SERVER_URL}/chat`, authData, { + headers: { + 'content-type': CONTENT_TYPE, + serverId: SERVER_ID, + Authorization: `Bearer ${accessToken}`, + }, + }); + return response.data; + } catch (error) { + console.log(error); + } +}; + +export const getAllGameRooms = async (accessToken: string) => { + const response = await axios.get(`${SERVER_URL}/chat/all`, { + headers: { + 'content-type': CONTENT_TYPE, + serverId: SERVER_ID, + Authorization: `Bearer ${accessToken}`, + }, + }); + return response.data; +}; + +export const participateGameRoom = async ( + chatId: string, + accessToken: string, +) => { + const authData = { + chatId, + }; + const response = await axios.patch( + `${SERVER_URL}/chat/participate`, + authData, + { + headers: { + 'content-type': CONTENT_TYPE, + serverId: SERVER_ID, + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + console.log(response.data); + return response.data; }; diff --git a/src/api/socket.ts b/src/api/socket.ts new file mode 100644 index 00000000..de619507 --- /dev/null +++ b/src/api/socket.ts @@ -0,0 +1,11 @@ +import { SERVER_ID, CONTENT_TYPE, SERVER_URL } from '../constant'; +import io from 'socket.io-client'; +export const socketLogin = (accessToken: string) => { + const socket = io(`${SERVER_URL}/server`, { + extraHeaders: { + Authorization: `Bearer ${accessToken}`, + serverId: SERVER_ID, + }, + }); + return socket; +}; diff --git a/src/components/layout/checkGameRoom.tsx b/src/components/layout/checkGameRoom.tsx new file mode 100644 index 00000000..55eda2e8 --- /dev/null +++ b/src/components/layout/checkGameRoom.tsx @@ -0,0 +1,54 @@ +import { useEffect } from 'react'; +import { useRecoilState } from 'recoil'; +import { allRoomState } from '../../states/atom'; +import { getAllGameRooms, participateGameRoom } from '../../api'; +import { useNavigate } from 'react-router-dom'; + +const CheckGameRoom = () => { + const token: any = localStorage.getItem('jwt'); + const navigate = useNavigate(); + const [allRooms, setAllRooms] = useRecoilState(allRoomState); + const fetchData = async () => { + try { + const allRoomsData = await getAllGameRooms(token); + setAllRooms(allRoomsData.chats); + } catch (error) { + console.error('데이터 가져오기 오류:', error); + } + }; + + useEffect(() => { + fetchData(); + }, [allRooms.length]); + + if (allRooms.length === 0) { + return
No rooms available or an error occurred.
; + } + + const handleParticipate = async (numberOfPeople: number, chatId: any) => { + if (numberOfPeople === 4) { + alert('방이 꽉 찼어요.'); + } else { + await participateGameRoom(chatId, token); + navigate(`/room/${chatId}`); + } + }; + + return ( + <> + + {allRooms.map((element, index) => ( +
handleParticipate(element.users.length, element.id)}> + {element.name} +
{element.users.length}
+ {element.users.length === 4 &&
Four users in this room
} +
{element.users[0].id}
+
+ ))} + + ); +}; + +export default CheckGameRoom; diff --git a/src/components/layout/createGameRoom.tsx b/src/components/layout/createGameRoom.tsx new file mode 100644 index 00000000..598ee99c --- /dev/null +++ b/src/components/layout/createGameRoom.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { createGameRooms } from '../../api'; +import { io } from 'socket.io-client'; +import { SERVER_URL, CONTENT_TYPE, SERVER_ID } from '../../constant'; +import { useRecoilState } from 'recoil'; +import { allRoomState } from '../../states/atom'; + +const CreateGameRoom = () => { + const [allRooms, setAllRooms] = useRecoilState(allRoomState); + const navigate = useNavigate(); + const [name, setName] = useState(''); + const [users, setUsers] = useState([]); + const [isPrivate, setIsPrivate] = useState(false); + const token: any = localStorage.getItem('jwt'); + + const onChange = (e: React.ChangeEvent) => { + const { value, name } = e.target; + console.log(value, name); + if (value === 'Private') { + setIsPrivate(true); + } else { + setIsPrivate(false); + } + }; + + const onChangeName = (e: React.ChangeEvent) => { + setName(e.target.value); + }; + + const submit = async (e: React.FormEvent) => { + e.preventDefault(); + const check = await createGameRooms(token, name, users, isPrivate); + if (check === undefined) { + alert('중복된 방이 있습니다.'); + } else { + alert('방 생성 성공.'); + const socket = io(`${SERVER_URL}/chat?chatId=${check.id}`, { + extraHeaders: { + Authorization: `Bearer ${token}`, + 'content-type': CONTENT_TYPE, + serverId: SERVER_ID, + }, + }); + console.log(socket.connected); + socket.on('message-to-client', (messageObject: any) => { + console.log(messageObject); + }); + + setAllRooms([...allRooms, check]); + + navigate(`/room/:${check.id}`); + } + }; + + return ( + <> +
CreateGameRoom
+
+ + + + + +
+ + ); +}; + +export default CreateGameRoom; diff --git a/src/components/layout/onineUserList.tsx b/src/components/layout/onineUserList.tsx new file mode 100644 index 00000000..1942262c --- /dev/null +++ b/src/components/layout/onineUserList.tsx @@ -0,0 +1,41 @@ +// import { } from '../../pages/login/userLogin'; + +// const OnlineUserList = async () => { +// const socket = sendingClient; +// if (socket) { +// socket.on('message-to-client', (messageObject: any) => { +// console.log(messageObject.data); +// }); +// } else { +// console.error( +// 'Socket is null. Socket connection might not be established yet.', +// ); +// } +// // const [allOnlineUsers, setAllOnlineUsers] = useRecoilState(onlineUserState); +// // const token: any = localStorage.getItem('jwt'); + +// // useEffect(() => { +// // const socket = socketLogin(token); + +// // const handleUsersUpdate = (responseData: any) => { +// // const usersArray = Array.isArray(responseData) ? responseData : []; +// // setAllOnlineUsers(usersArray); +// // }; + +// // socket.on('users-server-to-client', handleUsersUpdate); + +// // return () => { +// // socket.off('users-server-to-client', handleUsersUpdate); +// // }; +// // }, [setAllOnlineUsers, token]); + +// // console.log(allOnlineUsers); + +// return ( +// <> +//
1
+// +// ); +// }; + +// export default OnlineUserList; diff --git a/src/components/layout/userList.tsx b/src/components/layout/userList.tsx new file mode 100644 index 00000000..e1916193 --- /dev/null +++ b/src/components/layout/userList.tsx @@ -0,0 +1,34 @@ +import { useEffect } from 'react'; +import { useRecoilState } from 'recoil'; +import { allUserState } from '../../states/atom'; +import { getAllUsers } from '../../api'; + +const UserList = () => { + const [allUsers, setAllUsers] = useRecoilState(allUserState); + // const [allRooms, setAllRooms] = useRecoilState(allRoomState); + const token: any = localStorage.getItem('jwt'); + // const allUsers = useRecoilValue(allUserState); + console.log(allUsers); + useEffect(() => { + async function fetchData() { + try { + const allUsersData = await getAllUsers(token); + setAllUsers(allUsersData); + } catch (error) { + console.error('데이터 가져오기 오류:', error); + } + } + + fetchData(); + }, []); + + return ( + <> + {allUsers.map((element, index) => ( +
{element.name}
+ ))} + + ); +}; + +export default UserList; diff --git a/src/pages/lobby/gameLobby.tsx b/src/pages/lobby/gameLobby.tsx index 201ed431..051bc348 100644 --- a/src/pages/lobby/gameLobby.tsx +++ b/src/pages/lobby/gameLobby.tsx @@ -1,4 +1,7 @@ -import { getAllUsers } from '../../api/index'; +import CreateGameRoom from '../../components/layout/createGameRoom'; +import CheckGameRoom from '../../components/layout/checkGameRoom'; +// import OnlineUserList from '../../components/layout/onineUserList'; +// import UserList from '../../components/layout/userList'; const GameLobby = () => { const handleGetAllUsers = async (e: React.FormEvent) => { e.preventDefault(); @@ -20,9 +23,34 @@ const GameLobby = () => { } }; + // const [allUsers, setAllUsers] = useRecoilState(allUserState); + // const [allRooms, setAllRooms] = useRecoilState(allRoomState); + // const token: any = localStorage.getItem('jwt'); + + // useEffect(() => { + // async function fetchData() { + // try { + // const allUsersData = await getAllUsers(token); + // setAllUsers(allUsersData); + // const allRoomsData = await getAllGameRooms(token); + // setAllRooms(allRoomsData.chats); + // } catch (error) { + // console.error('데이터 가져오기 오류:', error); + // } + // } + + + // fetchData(); + // }, []); return ( <> - + {/* */} +

+ {/* */} +

+ +

+ ); }; diff --git a/src/pages/login/userLogin.tsx b/src/pages/login/userLogin.tsx index 4dccaf15..10954bc8 100644 --- a/src/pages/login/userLogin.tsx +++ b/src/pages/login/userLogin.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; + import { Link as ReactRouterLink, useNavigate } from 'react-router-dom'; import { Center, @@ -14,11 +15,13 @@ import { import { postLogin } from '../../api/index'; + const UserLogin = () => { const navigate = useNavigate(); const [id, setId] = useState(''); const [password, setPassword] = useState(''); + const token: any = localStorage.getItem('jwt'); const handleLoginSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -27,18 +30,7 @@ const UserLogin = () => { const { accessToken, refreshToken } = res.data; localStorage.setItem('accessToken', accessToken); alert('로그인에 성공했습니다.'); - navigate('/lobby'); - } catch (e: any) { - console.log(e.message); - if (e.message === 'Request failed with status code 400') { - alert('아이디를 확인해주세요.'); - } else if (e.message === 'Request failed with status code 401') { - alert('비밀번호를 확인해주세요.'); - } else { - alert('로그인에 실패했습니다.'); - } - } - }; + return (
({ + key: 'allUserState', + default: [], +}); + +export const allRoomState = atom({ + key: 'allRoomsState', + default: [], +}); + +export const onlineUserState = atom({ + key: 'onlineUserState', + default: [], +});