From e459ca45237f468dfe41c420d027409e15d2de71 Mon Sep 17 00:00:00 2001 From: Haaaan1 Date: Wed, 27 Mar 2024 13:23:06 +0100 Subject: [PATCH 1/2] Modify Lobby related navigation in all pages --- src/App.tsx | 3 +-- .../routing/routeProtectors/LoginGuard.js | 2 +- src/components/routing/routers/AppRouter.js | 18 +++++++++--------- src/components/views/Login.tsx | 4 ++-- src/components/views/Profile.tsx | 4 ++-- src/components/views/Register.tsx | 4 ++-- src/styles/_theme.scss | 4 ++-- src/styles/views/Login.scss | 4 ++-- 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 585a89a..17cd0f2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,4 @@ import React from "react"; -import Header from "./components/views/Header"; import AppRouter from "./components/routing/routers/AppRouter"; /** @@ -11,7 +10,7 @@ import AppRouter from "./components/routing/routers/AppRouter"; const App = () => { return (
-
+
); diff --git a/src/components/routing/routeProtectors/LoginGuard.js b/src/components/routing/routeProtectors/LoginGuard.js index facbbd2..cc25f38 100644 --- a/src/components/routing/routeProtectors/LoginGuard.js +++ b/src/components/routing/routeProtectors/LoginGuard.js @@ -13,7 +13,7 @@ export const LoginGuard = () => { return ; } - return ; + return ; }; LoginGuard.propTypes = { diff --git a/src/components/routing/routers/AppRouter.js b/src/components/routing/routers/AppRouter.js index 4c6dd0e..afed027 100644 --- a/src/components/routing/routers/AppRouter.js +++ b/src/components/routing/routers/AppRouter.js @@ -1,7 +1,7 @@ import React from "react"; import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom"; -import {GameGuard} from "../routeProtectors/GameGuard"; -import GameRouter from "./GameRouter"; +import {LobbyGuard} from "../routeProtectors/LobbyGuard"; +import LobbyRouter from "./LobbyRouter"; import {LoginGuard} from "../routeProtectors/LoginGuard"; import Register from "../../views/Register"; import Login from "../../views/Login"; @@ -10,10 +10,10 @@ import EditProfile from "../../views/Editprofile"; /** * Main router of your application. * In the following class, different routes are rendered. In our case, there is a Login Route with matches the path "/login" - * and another Router that matches the route "/game". + * and another Router that matches the route "/lobby". * The main difference between these two routes is the following: * /login renders another component without any sub-route - * /game renders a Router that contains other sub-routes that render in turn other react components + * /lobby renders a Router that contains other sub-routes that render in turn other react components * Documentation about routing in React: https://reactrouter.com/en/main/start/tutorial */ const AppRouter = () => { @@ -21,8 +21,8 @@ const AppRouter = () => { - }> - } /> + }> + } /> }> @@ -34,16 +34,16 @@ const AppRouter = () => { {/* guard to user profile page */} - }> + }> } /> - }> + }> } /> + }/> diff --git a/src/components/views/Login.tsx b/src/components/views/Login.tsx index e3f8317..c848e5f 100644 --- a/src/components/views/Login.tsx +++ b/src/components/views/Login.tsx @@ -53,8 +53,8 @@ const Login = () => { localStorage.setItem("id",user.id); localStorage.setItem("username",user.username); - // Login successfully worked --> navigate to the route /game in the GameRouter - navigate("/game"); + // Login successfully worked --> navigate to the route /game in the LobbyRouter + navigate("/lobby"); } catch (error) { alert( `Something went wrong during the login: \n${handleError(error)}` diff --git a/src/components/views/Profile.tsx b/src/components/views/Profile.tsx index 250a2c2..a5ebbae 100644 --- a/src/components/views/Profile.tsx +++ b/src/components/views/Profile.tsx @@ -23,7 +23,7 @@ const Profile = () => { } catch (error) { console.error(`Fetching user data failed: ${handleError(error)}`); alert(`Something went wrong during Fetching user: \n${handleError(error)}`); - navigate("/game"); + navigate("/lobby"); } } @@ -64,7 +64,7 @@ const Profile = () => {
{user ? : }
- +
diff --git a/src/components/views/Register.tsx b/src/components/views/Register.tsx index 56c69f4..4987790 100644 --- a/src/components/views/Register.tsx +++ b/src/components/views/Register.tsx @@ -52,8 +52,8 @@ const Register = () => { localStorage.setItem("id", user.id); // Store the username into the local storage. localStorage.setItem("username", user.username); - // Login successfully worked --> navigate to the route /game in the GameRouter - navigate("/game"); + // Login successfully worked --> navigate to the route /game in the LobbyRouter + navigate("/lobby"); } catch (error) { alert( `Something went wrong during the login: \n${handleError(error)}` diff --git a/src/styles/_theme.scss b/src/styles/_theme.scss index 406a04c..a22e43f 100644 --- a/src/styles/_theme.scss +++ b/src/styles/_theme.scss @@ -8,8 +8,8 @@ feel free to play around with these and see what other look you can come up with! :D */ -$accent: #FFCC4D; -$accentDark: #FFCC4D; +$accent: white; +$accentDark: white; $textColor: black; $background: #C9D7DD; $stroke: #637A9F; diff --git a/src/styles/views/Login.scss b/src/styles/views/Login.scss index aa044d5..933be87 100644 --- a/src/styles/views/Login.scss +++ b/src/styles/views/Login.scss @@ -1,8 +1,8 @@ -@import 'styles/theme'; +@import '../theme'; .login { &.container { - + min-height: 300px; /* we include the center mixin defined in _theme.scss and save three duplicate lines :) */ @include center; From 6723514c64d0d30c594243c7c3bff5834e62793c Mon Sep 17 00:00:00 2001 From: Haaaan1 Date: Wed, 27 Mar 2024 13:25:37 +0100 Subject: [PATCH 2/2] Implement Lobby page #40 #1 #8 #29 --- .../{GameGuard.js => LobbyGuard.js} | 6 +- .../routers/{GameRouter.js => LobbyRouter.js} | 12 +- src/components/views/Game.tsx | 134 ------------ src/components/views/Lobby.tsx | 181 +++++++++++++++++ src/models/Room.js | 19 ++ src/styles/views/Game.scss | 65 ------ src/styles/views/Lobby.scss | 190 ++++++++++++++++++ src/types.ts | 13 ++ 8 files changed, 412 insertions(+), 208 deletions(-) rename src/components/routing/routeProtectors/{GameGuard.js => LobbyGuard.js} (77%) rename src/components/routing/routers/{GameRouter.js => LobbyRouter.js} (64%) delete mode 100644 src/components/views/Game.tsx create mode 100644 src/components/views/Lobby.tsx create mode 100644 src/models/Room.js delete mode 100644 src/styles/views/Game.scss create mode 100644 src/styles/views/Lobby.scss diff --git a/src/components/routing/routeProtectors/GameGuard.js b/src/components/routing/routeProtectors/LobbyGuard.js similarity index 77% rename from src/components/routing/routeProtectors/GameGuard.js rename to src/components/routing/routeProtectors/LobbyGuard.js index 8e6b89a..7c03ec1 100644 --- a/src/components/routing/routeProtectors/GameGuard.js +++ b/src/components/routing/routeProtectors/LobbyGuard.js @@ -6,12 +6,12 @@ import PropTypes from "prop-types"; * routeProtectors interfaces can tell the router whether or not it should allow navigation to a requested route. * They are functional components. Based on the props passed, a route gets rendered. * In this case, if the user is authenticated (i.e., a token is stored in the local storage) - * is rendered --> The content inside the in the App.js file, i.e. the user is able to access the main app. + * is rendered --> The content inside the in the App.js file, i.e. the user is able to access the main app. * If the user isn't authenticated, the components redirects to the /login screen * @Guard * @param props */ -export const GameGuard = () => { +export const LobbyGuard = () => { if (localStorage.getItem("token")) { return ; @@ -20,6 +20,6 @@ export const GameGuard = () => { return ; }; -GameGuard.propTypes = { +LobbyGuard.propTypes = { children: PropTypes.node }; \ No newline at end of file diff --git a/src/components/routing/routers/GameRouter.js b/src/components/routing/routers/LobbyRouter.js similarity index 64% rename from src/components/routing/routers/GameRouter.js rename to src/components/routing/routers/LobbyRouter.js index 0c3ddd1..0fce7a5 100644 --- a/src/components/routing/routers/GameRouter.js +++ b/src/components/routing/routers/LobbyRouter.js @@ -1,16 +1,16 @@ import React from "react"; import {Navigate, Route, Routes} from "react-router-dom"; -import Game from "../../views/Game"; +import Lobby from "../../views/Lobby"; import PropTypes from "prop-types"; -const GameRouter = () => { +const LobbyRouter = () => { return (
- } /> + } /> - } /> + } /> } /> @@ -23,8 +23,8 @@ const GameRouter = () => { * Don't forget to export your component! */ -GameRouter.propTypes = { +LobbyRouter.propTypes = { base: PropTypes.string } -export default GameRouter; +export default LobbyRouter; diff --git a/src/components/views/Game.tsx b/src/components/views/Game.tsx deleted file mode 100644 index c2bc7e8..0000000 --- a/src/components/views/Game.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { api, handleError } from "helpers/api"; -import { Spinner } from "components/ui/Spinner"; -import { Button } from "components/ui/Button"; -import {useNavigate} from "react-router-dom"; -import BaseContainer from "components/ui/BaseContainer"; -import PropTypes from "prop-types"; -import "styles/views/Game.scss"; -import { User } from "types"; - -const Player = ({ user }: { user: User }) => ( -
-
-
id: {user.id}
- -
Creation Date: {user.registerDate}
-
-
- {user.status} -
-
-); - -Player.propTypes = { - user: PropTypes.object, -}; - -const Game = () => { - //test commit3 - //test commit2 - // use react-router-dom"s hook to access navigation, more info: https://reactrouter.com/en/main/hooks/use-navigate - const navigate = useNavigate(); - - // define a state variable (using the state hook). - // if this variable changes, the component will re-render, but the variable will - // keep its value throughout render cycles. - // a component can have as many state variables as you like. - // more information can be found under https://react.dev/learn/state-a-components-memory and https://react.dev/reference/react/useState - const [users, setUsers] = useState(null); - - // const logout = (): void => { - // localStorage.removeItem("token"); - // navigate("/login"); - // }; - - const logout = async () => { - const id = localStorage.getItem("id"); - localStorage.removeItem("token"); - //apply a post request for user logout - try { - const requestBody = JSON.stringify({id:id}); - const response = await api.post("/users/logout", requestBody); - console.log(response); - - } catch (error) { - alert(`Something went wrong during the logout: \n${handleError(error)}`); - } - navigate("/login"); - }; - - // the effect hook can be used to react to change in your component. - // in this case, the effect hook is only run once, the first time the component is mounted - // this can be achieved by leaving the second argument an empty array. - // for more information on the effect hook, please see https://react.dev/reference/react/useEffect - useEffect(() => { - // effect callbacks are synchronous to prevent race conditions. So we put the async function inside: - async function fetchData() { - try { - const response = await api.get("/users"); - - // delays continuous execution of an async operation for 1 second. - // This is just a fake async call, so that the spinner can be displayed - // feel free to remove it :) - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // Get the returned users and update the state. - setUsers(response.data); - - // This is just some data for you to see what is available. - // Feel free to remove it. - console.log("request to:", response.request.responseURL); - console.log("status code:", response.status); - console.log("status text:", response.statusText); - console.log("requested data:", response.data); - - // See here to get more data. - console.log(response); - } catch (error) { - console.error( - `Something went wrong while fetching the users: \n${handleError( - error - )}` - ); - console.error("Details:", error); - alert( - "Something went wrong while fetching the users! See the console for details." - ); - } - } - - fetchData(); - }, []); - - let content = ; - - if (users) { - content = ( -
-
    - {users.map((user: User) => ( -
  • - -
  • - ))} -
- -
- ); - } - - return ( - -

Happy Coding!

-

- Get all users from secure endpoint: -

- {content} -
- ); -}; - -export default Game; diff --git a/src/components/views/Lobby.tsx b/src/components/views/Lobby.tsx new file mode 100644 index 0000000..c7e6de5 --- /dev/null +++ b/src/components/views/Lobby.tsx @@ -0,0 +1,181 @@ +import React, { useEffect, useState } from "react"; +import { api, handleError } from "helpers/api"; +import { Spinner } from "components/ui/Spinner"; +import { Button } from "components/ui/Button"; +import {useNavigate} from "react-router-dom"; +import BaseContainer from "components/ui/BaseContainer"; +import PropTypes from "prop-types"; +import "styles/views/Lobby.scss"; +import { User, Room } from "types"; +type PlayerProps = { + user: User; +}; +type RoomComponentProps = { + room: Room; +}; +type RoomListProps = { + rooms: Room[]; +}; +const Player: React.FC = ({ user }) => ( +
+ {user.username} +
{user.username}
+
+); +const RoomComponent: React.FC = ({ room }) => ( +
+
+ {room.roomPlayersList.map(user => ( + + ))} +
+
+
{room.theme}
+
+ {room.status} +
+
+
+); +const RoomList: React.FC = ({ rooms }) => ( +
+ {rooms.map(room => ( + + ))} +
+); + +Player.propTypes = { + user: PropTypes.object, +}; + +const mockRoomPlayers: User[] = [ + { id: 1, username: 'Alice', avatar: 'https://twemoji.maxcdn.com/v/latest/72x72/1f604.png', name: 'Alice Wonderland', status: 'ONLINE', registerDate: new Date('2021-08-01'), birthday: new Date('1990-01-01') }, + { id: 2, username: 'Bob', avatar: 'https://twemoji.maxcdn.com/v/latest/72x72/1f602.png', name: 'Bob Builder', status: 'OFFLINE', registerDate: new Date('2021-09-01'), birthday: new Date('1985-02-02') }, +]; + +const mockRooms: Room[] = [ + { + id: '1', + roomOwnerId: '1', + roomPlayersList: [mockRoomPlayers[0]], + theme: 'Advanced', + status: 'In Game', + maxPlayersNum: 4, + alivePlayersList: [mockRoomPlayers[0]], + currentPlayerIndex: 0, + playToOuted: false, + }, + { + id: '2', + roomOwnerId: '2', + roomPlayersList: [mockRoomPlayers[1]], + theme: 'Food', + status: 'Free', + maxPlayersNum: 4, + alivePlayersList: [mockRoomPlayers[1]], + currentPlayerIndex: 1, + playToOuted: false, + } +]; + + +const Lobby = () => { + const navigate = useNavigate(); + + const [rooms, setRooms] = useState(mockRooms); + const [user, setUser] = useState(mockRoomPlayers[0]); + const logout = async () => { + const id = localStorage.getItem("id"); + localStorage.removeItem("token"); + //apply a post request for user logout + try { + const requestBody = JSON.stringify({id:id}); + const response = await api.post("/users/logout", requestBody); + console.log(response); + + } catch (error) { + alert(`Something went wrong during the logout: \n${handleError(error)}`); + } + navigate("/login"); + }; + + // useEffect(() => { + // async function fetchData() { + // try { + // //get all rooms + // const response = await api.get("/games/lobby"); + // await new Promise((resolve) => setTimeout(resolve, 1000)); + // setRooms(response.data); + // + // console.log("request to:", response.request.responseURL); + // console.log("status code:", response.status); + // console.log("status text:", response.statusText); + // console.log("requested data:", response.data); + // + // // See here to get more data. + // console.log(response); + // } catch (error) { + // console.error( + // `Something went wrong while fetching the users: \n${handleError( + // error + // )}` + // ); + // console.error("Details:", error); + // alert( + // "Something went wrong while fetching the users! See the console for details." + // ); + // } + // } + // + // fetchData(); + // }, []); + + const userinfo = () =>{ + return + } + const renderRoomLists = () => { + return mockRooms.map(room => ( +
+
+ {room.roomPlayersList?.map((user, index) => ( +
+ {user.name} +
{user.username}
+
+ ))} +
+
+ ROOM #{room.id} +
{room.theme}
+ + + {room.status} + + +
+
+ )); + }; + return ( + + + +
+ {user.name} +
{user.username}
+
+
+ Kaeps +
+
i
+
+

Rooms

+ {renderRoomLists()} +
+ +
+ ); +}; + +export default Lobby; diff --git a/src/models/Room.js b/src/models/Room.js new file mode 100644 index 0000000..30c7b25 --- /dev/null +++ b/src/models/Room.js @@ -0,0 +1,19 @@ +/** + * Room model + */ +class Room { + constructor(data = {}) { + this.id = null; + this.roomOwnerId = null; + this.roomPlayersList = null; + this.theme = null; + this.status = null; + this.maxPlayersNum = null; + this.alivePlayersList = null; + this.currentPlayerIndex = null; + this.playToOuted = null; + Object.assign(this, data); + } +} + +export default Room; \ No newline at end of file diff --git a/src/styles/views/Game.scss b/src/styles/views/Game.scss deleted file mode 100644 index 219159e..0000000 --- a/src/styles/views/Game.scss +++ /dev/null @@ -1,65 +0,0 @@ -@import "../theme"; - -/* The '&' is a shorthand for the containing element, - so for example a '&.user-list' inside .game will compile to - .game.user-list - - for more information visit https://sass-lang.com/guide - */ - -.game { - &.container { - background: $background; - padding: 1.5em; - border-radius: $borderRadius; - display: flex; - flex-direction: column; - align-items: center; - box-shadow: $dropShadow; - } - &.user-list { - list-style: none; - padding-left: 0; - } - &.user-item { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - } - & p,h2 { - margin: 0.5em 0; - } -} - -.player { - &.container { - margin: 0.5em 0; - width: 20em; - padding: 10px; - border-radius: $borderRadius; - display: flex; - align-items: center; - background: lighten($background, 5); - } - &.status { - font-weight: 600; - margin-left: 50%; - } - &.namelink { - color: white; - } - &.namelink:hover { - color: green; - } - &.username { - font-weight: 500; - color: $accent; - } - &.id { - margin-left: auto; - margin-right: 10px; - font-family: $fontMono; - font-weight: 300; - } -} \ No newline at end of file diff --git a/src/styles/views/Lobby.scss b/src/styles/views/Lobby.scss new file mode 100644 index 0000000..92c1af2 --- /dev/null +++ b/src/styles/views/Lobby.scss @@ -0,0 +1,190 @@ +@import "../theme"; +@import url('https://fonts.googleapis.com/css2?family=Acme&display=swap'); +/* The '&' is a shorthand for the containing element, + so for example a '&.user-list' inside .game will compile to + .game.user-list + + for more information visit https://sass-lang.com/guide + */ + +.lobby { + &.container { + background: rgba(255, 255, 255); + padding: 1.5em; + border-radius: $borderRadius; + display: flex; + flex-direction: column; + align-items: center; + box-shadow: $dropShadow; + } + &.room-list{ + position: absolute; // 使用绝对定位 + right: 3%; // 定位到右边 + top: 10%; // 从顶部留下10%的空间以居中显示 + width: 40%; // 宽度为视口宽度的40% + height: 80%; // 高度为视口高度的80% + display: flex; + flex-direction: column; + background-color: #ffcc66; + padding: 10px; + border-radius: 1.5em; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow-y: auto; + } + &.user-list { + list-style: none; + padding-left: 0; + } + &.user-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + & p,h2 { + margin: 0.5em 0; + } +} +.room-container{ + display: flex; + margin-bottom: 20px; + background: #fff6e5; + border-radius: 10px; + padding: 10px; + justify-content: space-between; +} +.room { + background-color: #fff; + margin-bottom: 10px; + border-radius: 8px; + //overflow: hidden; + + .room-header, .room-players { + flex: 1; // Allow both children to grow and take up equal space + display: flex; + flex-direction: column; // Stack the children of these containers vertically + align-items: center; // Center the items horizontally + justify-content: center; // Center the items vertically + padding: 10px; + } + + .room-players { + align-items: flex-start; // Align the players to the start of the flex container + } + + .room-header { + align-items: flex-end; // Align the room details to the end of the flex container + } + + .player { + margin-right: 10px; + text-align: center; + + .player-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: #D9D9D9; // 默认背景色 + } + + .player-username { + font-size: 0.8em; + } + } + + .room-footer { + display: flex; + justify-content: space-between; + padding: 10px; + background-color: #fff; + + .room-theme { + font-weight: bold; + } + + } +} +.status-container { + width: 18%; /* 或者任何固定宽度 */ + display: inline-block; /* 或者 block,取决于你的布局需求 */ +} +.room-status { + padding: 4px; + width: 70px; + display: inline-block; + text-align: center; + &.in-game { + border-radius: $borderRadius; + background-color: orange; + } + &.free { + + border-radius: $borderRadius; + background-color: lightgreen; + } +} +.player { + &.container { + margin: 0.5em 0; + width: 20em; + padding: 10px; + border-radius: $borderRadius; + display: flex; + align-items: center; + background: lighten($background, 5); + } + &.status { + font-weight: 600; + margin-left: 50%; + } + &.namelink { + color: white; + } + &.namelink:hover { + color: green; + } + &.id { + margin-left: auto; + margin-right: 10px; + font-family: $fontMono; + font-weight: 300; + } +} +.name { + font-weight: 500; + color: black; + text-align: center; +} + +.user-container{ + text-align: center; + position: absolute; // 使用绝对定位 + left: 3%; // 定位到zuo边 + top: 10%; // 从顶部留下10%的空间以居中显示 + width: 100px; // 宽度为视口宽度的40% + height: 100px; // 高度为视口高度的80% + background: #D9D9D9; + border-radius: $borderRadius; +} + +.big-title{ + position: absolute; // 使用绝对定位 + left: 15%; // 定位到zuo边 + top: 25%; // 从顶部留下10%的空间以居中显示 + font-size: 250px; + color: #ffcc66; + font-family: "Acme", sans-serif; +} + +.information{ + position: absolute; // 使用绝对定位 + left: 48%; // 定位到zuo边 + top: 24%; + font-size: 43px; + width: 50px; + text-align: center; + color: white; + font-family: "Acme", sans-serif; + background: #ffcc66; + border-radius: $borderRadius; +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 52e5e59..1cdefbc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,21 @@ export type User = { status: any; username: string; + avatar: string; name: string; id: number; registerDate: Date; birthday: Date; }; + +export type Room = { + id: string | null; + roomOwnerId: string | null; + roomPlayersList: User[] | null; + theme: string | null; + status: string | null; + maxPlayersNum: number | null; + alivePlayersList: User[] | null; + currentPlayerIndex: number | null; + playToOuted: boolean | null; +};