From 8c57670742d142d47beb06494c582dbda08cc6a6 Mon Sep 17 00:00:00 2001
From: Ralex91 <95540504+Ralex91@users.noreply.github.com>
Date: Sat, 3 Feb 2024 00:14:04 +0100
Subject: [PATCH] Add Kick player & Validators
---
package-lock.json | 30 ++++++++++++++++++++
package.json | 1 +
socket/package-lock.json | 40 ++++++++++++++++++++++++++-
socket/package.json | 3 +-
socket/src/index.js | 2 +-
socket/src/roles/player.js | 36 ++++++++++++++++++------
socket/src/utils/cooldown.js | 2 +-
socket/src/validator.js | 12 ++++++++
src/components/Toaster.jsx | 26 +++++++++++++++++
src/components/game/GameWrapper.jsx | 13 ++++++++-
src/components/game/join/Room.jsx | 2 +-
src/components/game/join/Username.jsx | 23 ++++++++++-----
src/components/game/states/Room.jsx | 5 ++++
src/pages/_app.js | 2 ++
src/pages/game.jsx | 13 +++++----
src/pages/index.js | 7 +++--
src/pages/managerold.jsx | 2 +-
src/validator.js | 0
18 files changed, 188 insertions(+), 31 deletions(-)
create mode 100644 socket/src/validator.js
create mode 100644 src/components/Toaster.jsx
delete mode 100644 src/validator.js
diff --git a/package-lock.json b/package-lock.json
index 1912f20..0a9421f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"next": "14.1.0",
"react": "^18",
"react-dom": "^18",
+ "react-hot-toast": "^2.4.1",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.4"
},
@@ -1222,6 +1223,12 @@
"node": ">=4"
}
},
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "peer": true
+ },
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -2303,6 +2310,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/goober": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz",
+ "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -3820,6 +3835,21 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-hot-toast": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz",
+ "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==",
+ "dependencies": {
+ "goober": "^2.1.10"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16",
+ "react-dom": ">=16"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
diff --git a/package.json b/package.json
index 2eda4a8..646951a 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"next": "14.1.0",
"react": "^18",
"react-dom": "^18",
+ "react-hot-toast": "^2.4.1",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.4"
},
diff --git a/socket/package-lock.json b/socket/package-lock.json
index f0e358b..254df4b 100644
--- a/socket/package-lock.json
+++ b/socket/package-lock.json
@@ -7,7 +7,8 @@
"": {
"version": "0.1.0",
"dependencies": {
- "socket.io": "^4.7.4"
+ "socket.io": "^4.7.4",
+ "yup": "^1.3.3"
}
},
"node_modules/@socket.io/component-emitter": {
@@ -160,6 +161,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/property-expr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
+ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
+ },
"node_modules/socket.io": {
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.4.tgz",
@@ -197,6 +203,27 @@
"node": ">=10.0.0"
}
},
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
+ },
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
+ },
+ "node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
@@ -229,6 +256,17 @@
"optional": true
}
}
+ },
+ "node_modules/yup": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.3.3.tgz",
+ "integrity": "sha512-v8QwZSsHH2K3/G9WSkp6mZKO+hugKT1EmnMqLNUcfu51HU9MDyhlETT/JgtzprnrnQHPWsjc6MUDMBp/l9fNnw==",
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
}
}
}
diff --git a/socket/package.json b/socket/package.json
index e0a0c91..90fe8e9 100644
--- a/socket/package.json
+++ b/socket/package.json
@@ -6,6 +6,7 @@
"start": "node ./src/index.js"
},
"dependencies": {
- "socket.io": "^4.7.4"
+ "socket.io": "^4.7.4",
+ "yup": "^1.3.3"
}
}
diff --git a/socket/src/index.js b/socket/src/index.js
index 3114155..1323832 100644
--- a/socket/src/index.js
+++ b/socket/src/index.js
@@ -29,7 +29,7 @@ io.on("connection", (socket) => {
Manager.createRoom(gameState, io, socket),
)
socket.on("manager:kickPlayer", (playerId) =>
- Manager.kickPlayer(gameState, socket, io, playerId),
+ Manager.kickPlayer(gameState, io, socket, playerId),
)
socket.on("manager:startGame", () => Manager.startGame(gameState, io, socket))
diff --git a/socket/src/roles/player.js b/socket/src/roles/player.js
index 3218c65..5b7c20f 100644
--- a/socket/src/roles/player.js
+++ b/socket/src/roles/player.js
@@ -1,23 +1,43 @@
import convertTimeToPoint from "../utils/convertTimeToPoint.js"
import { abortCooldown } from "../utils/cooldown.js"
+import { inviteCodeValidator, usernameValidator } from "../validator.js"
const Player = {
- checkRoom: (game, io, socket, roomId) => {
- if (!game.room || roomId !== game.room) {
- io.to(socket.id).emit("game:errorMessage", "Room not found")
- console.log("zaza")
+ checkRoom: async (game, io, socket, roomId) => {
+ try {
+ await inviteCodeValidator.validate(roomId)
+ } catch (error) {
+ socket.emit("game:errorMessage", error.errors[0])
return
}
- io.to(socket.id).emit("game:successRoom", roomId)
+ if (!game.room || roomId !== game.room) {
+ socket.emit("game:errorMessage", "Room not found")
+ return
+ }
+
+ socket.emit("game:successRoom", roomId)
},
- join: (game, io, socket, player) => {
- if (!player.room || !player.room || game.started) {
+ join: async (game, io, socket, player) => {
+ try {
+ await usernameValidator.validate(player.username)
+ } catch (error) {
+ socket.emit("game:errorMessage", error.errors[0])
return
}
- console.log("joined", player)
+ if (!game.room || player.room !== game.room) {
+ socket.emit("game:errorMessage", "Room not found")
+ return
+ }
+
+ if (game.started) {
+ socket.emit("game:errorMessage", "Game already started")
+ return
+ }
+
+ console.log("New Player", player)
socket.join(player.room)
diff --git a/socket/src/utils/cooldown.js b/socket/src/utils/cooldown.js
index de2dcd3..2a55cdf 100644
--- a/socket/src/utils/cooldown.js
+++ b/socket/src/utils/cooldown.js
@@ -23,4 +23,4 @@ export const cooldown = (ms, io, room) => {
})
}
-export const sleep = (sec) => new Promise((r) => setTimeout(r, sec * 1000))
\ No newline at end of file
+export const sleep = (sec) => new Promise((r) => setTimeout(r, sec * 1000))
diff --git a/socket/src/validator.js b/socket/src/validator.js
new file mode 100644
index 0000000..2c6f0dc
--- /dev/null
+++ b/socket/src/validator.js
@@ -0,0 +1,12 @@
+import yup from "yup"
+
+export const usernameValidator = yup
+ .string()
+ .required("Username is required")
+ .min(4, "Username cannot be less than 4 characters")
+ .max(20, "Username cannot exceed 20 characters")
+
+export const inviteCodeValidator = yup
+ .string()
+ .required("Invite code is required")
+ .length(6, "Invalid invite code")
diff --git a/src/components/Toaster.jsx b/src/components/Toaster.jsx
new file mode 100644
index 0000000..1d14263
--- /dev/null
+++ b/src/components/Toaster.jsx
@@ -0,0 +1,26 @@
+import clsx from "clsx"
+import { Toaster as ToasterRaw, ToastBar } from "react-hot-toast"
+
+const Toaster = () => (
+
+ {(t) => (
+
+ {({ icon, message }) => (
+ <>
+ {icon}
+ {message}
+ >
+ )}
+
+ )}
+
+)
+
+export default Toaster
diff --git a/src/components/game/GameWrapper.jsx b/src/components/game/GameWrapper.jsx
index 9b7de9e..7bdb0dd 100644
--- a/src/components/game/GameWrapper.jsx
+++ b/src/components/game/GameWrapper.jsx
@@ -4,14 +4,24 @@ import background from "@/assets/background.webp"
import { usePlayerContext } from "@/context/player"
import { useSocketContext } from "@/context/socket"
import { useEffect, useState } from "react"
+import { useRouter } from "next/router"
export default function GameWrapper({ children, textNext, onNext, manager }) {
const { socket } = useSocketContext()
- const { player } = usePlayerContext()
+ const { player, dispatch } = usePlayerContext()
+ const router = useRouter()
const [questionState, setQuestionState] = useState()
useEffect(() => {
+ socket.on("game:kick", () => {
+ dispatch({
+ type: "LOGOUT",
+ })
+
+ router.replace("/")
+ })
+
socket.on("game:updateQuestion", ({ current, total }) => {
setQuestionState({
current,
@@ -20,6 +30,7 @@ export default function GameWrapper({ children, textNext, onNext, manager }) {
})
return () => {
+ socket.off("game:kick")
socket.off("game:updateQuestion")
}
}, [])
diff --git a/src/components/game/join/Room.jsx b/src/components/game/join/Room.jsx
index ac89326..d1cd28c 100644
--- a/src/components/game/join/Room.jsx
+++ b/src/components/game/join/Room.jsx
@@ -2,7 +2,7 @@ import { usePlayerContext } from "@/context/player"
import Form from "@/components/Form"
import Button from "@/components/Button"
import Input from "@/components/Input"
-import { useEffect, useMemo, useState } from "react"
+import { useEffect, useState } from "react"
import { socket } from "@/context/socket"
export default function Room() {
diff --git a/src/components/game/join/Username.jsx b/src/components/game/join/Username.jsx
index c7ec880..b82fc58 100644
--- a/src/components/game/join/Username.jsx
+++ b/src/components/game/join/Username.jsx
@@ -2,7 +2,7 @@ import { usePlayerContext } from "@/context/player"
import Form from "@/components/Form"
import Button from "@/components/Button"
import Input from "@/components/Input"
-import { useState } from "react"
+import { useEffect, useState } from "react"
import { useSocketContext } from "@/context/socket"
import { useRouter } from "next/router"
@@ -14,14 +14,23 @@ export default function Username() {
const [username, setUsername] = useState("")
const handleJoin = () => {
- dispatch({
- type: "LOGIN",
- payload: username,
+ socket.emit("player:join", { username: username, room: player.room })
+ }
+
+ useEffect(() => {
+ socket.on("game:successJoin", () => {
+ dispatch({
+ type: "LOGIN",
+ payload: username,
+ })
+
+ router.replace("/game")
})
- socket.emit("player:join", { username: username, room: player.room })
- router.push("/game")
- }
+ return () => {
+ socket.off("game:successJoin")
+ }
+ }, [username])
return (
<>
diff --git a/src/components/game/states/Room.jsx b/src/components/game/states/Room.jsx
index c46b1bf..01bb275 100644
--- a/src/components/game/states/Room.jsx
+++ b/src/components/game/states/Room.jsx
@@ -10,8 +10,13 @@ export default function Room({ data: { text, inviteCode } }) {
setPlayerList([...playerList, player])
})
+ socket.on("manager:playerKicked", (playerId) => {
+ setPlayerList(playerList.filter((p) => p.id !== playerId))
+ })
+
return () => {
socket.off("manager:newPlayer")
+ socket.off("manager:playerKicked")
}
}, [playerList])
diff --git a/src/pages/_app.js b/src/pages/_app.js
index 53f9f48..f0129a7 100644
--- a/src/pages/_app.js
+++ b/src/pages/_app.js
@@ -1,3 +1,4 @@
+import Toaster from "@/components/Toaster"
import { PlayerContextProvider } from "@/context/player"
import { SocketContextProvider } from "@/context/socket"
import "@/styles/globals.css"
@@ -26,6 +27,7 @@ export default function App({ Component, pageProps }) {
+
>
)
}
diff --git a/src/pages/game.jsx b/src/pages/game.jsx
index 6dd24e3..a8f4205 100644
--- a/src/pages/game.jsx
+++ b/src/pages/game.jsx
@@ -9,7 +9,7 @@ import Start from "@/components/game/states/Start"
import { usePlayerContext } from "@/context/player"
import { useSocketContext } from "@/context/socket"
import { useRouter } from "next/router"
-import { createElement, useMemo, useState } from "react"
+import { createElement, useEffect, useState } from "react"
const gameStateComponent = {
SELECT_ANSWER: Answers,
@@ -26,10 +26,11 @@ export default function Game() {
const { socket } = useSocketContext()
const { player } = usePlayerContext()
- if (!player) {
- //router.push("/")
- return
- }
+ useEffect(() => {
+ if (!player) {
+ router.replace("/")
+ }
+ }, [])
const [state, setState] = useState({
status: {
@@ -42,7 +43,7 @@ export default function Game() {
},
})
- useMemo(() => {
+ useEffect(() => {
socket.on("game:status", (status) => {
setState({
...state,
diff --git a/src/pages/index.js b/src/pages/index.js
index e233b43..54b63ef 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -4,12 +4,13 @@ import Form from "@/components/Form"
import Button from "@/components/Button"
import Input from "@/components/Input"
import logo from "@/assets/logo.svg"
-import { useEffect, useMemo, useState } from "react"
+import { useEffect, useState } from "react"
import Loader from "@/components/Loader"
import { usePlayerContext } from "@/context/player"
import Room from "@/components/game/join/Room"
import Username from "@/components/game/join/Username"
import { useSocketContext } from "@/context/socket"
+import toast from "react-hot-toast"
export default function Home() {
const [loading, setLoading] = useState(false)
@@ -17,9 +18,9 @@ export default function Home() {
const { player, dispatch } = usePlayerContext()
const { socket } = useSocketContext()
- useMemo(() => {
+ useEffect(() => {
socket.on("game:errorMessage", (message) => {
- console.log(message)
+ toast.error(message)
})
return () => {
diff --git a/src/pages/managerold.jsx b/src/pages/managerold.jsx
index b1a1599..6d12aba 100644
--- a/src/pages/managerold.jsx
+++ b/src/pages/managerold.jsx
@@ -4,7 +4,7 @@ import Leaderboard from "@/components/game/states/Leaderboard"
import Question from "@/components/game/states/Question"
import Wait from "@/components/game/states/Wait"
import { useSocketContext } from "@/context/socket"
-import { createElement, useEffect, useMemo, useState } from "react"
+import { createElement, useEffect, useState } from "react"
export default function Manager({ children }) {
const { socket } = useSocketContext()
diff --git a/src/validator.js b/src/validator.js
deleted file mode 100644
index e69de29..0000000