diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c6bba59
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,130 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/traque-back/.gitignore b/traque-back/.gitignore
new file mode 100644
index 0000000..c6bba59
--- /dev/null
+++ b/traque-back/.gitignore
@@ -0,0 +1,130 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/traque-back/game.js b/traque-back/game.js
new file mode 100644
index 0000000..4a170cf
--- /dev/null
+++ b/traque-back/game.js
@@ -0,0 +1,106 @@
+import { Socket } from "socket.io";
+
+export default class Game {
+ constructor() {
+ this.teams = [];
+ this.started = false;
+ }
+
+ start() {
+ this.started = true;
+ }
+
+ stop() {
+ this.started = false;
+ }
+
+ getNewTeamId() {
+ let id = null;
+ while(id === null || this.teams.find(t => t.id === id)) {
+ id = Math.floor(Math.random() * 1000000);
+ }
+ return id;
+ }
+
+ addTeam(teamName) {
+ let id = this.getNewTeamId();
+ this.teams.push({
+ id: id,
+ name: teamName,
+ chasing: null,
+ chased: null,
+ currentLocation: [0, 0],
+ lastSentLocation: [0, 0],
+ enemyLocation: [0, 0]
+ });
+ this.updateTeamChasing();
+ return true;
+ }
+
+ updateTeamChasing() {
+ if(this.teams.length <= 1) {
+ return false;
+ }
+ this.teams[0].chased = this.teams[this.teams.length - 1].id;
+ this.teams[this.teams.length - 1].chasing = this.teams[0].id;
+
+ for(let i = 0; i < this.teams.length - 1; i++) {
+ this.teams[i].chasing = this.teams[i + 1].id;
+ this.teams[i+1].chased = this.teams[i].id;
+ }
+ return true;
+ }
+
+ reorderTeams(newOrder) {
+ this.teams = newOrder;
+ return this.updateTeamChasing();
+ }
+
+ getTeam(teamId) {
+ return this.teams.find(t => t.id === teamId);
+ }
+
+ renameTeam(teamId, newName) {
+ let team = this.getTeam(teamId);
+ if(team == undefined) {
+ return false;
+ }
+ team.name = newName;
+ return true;
+ }
+
+ updateLocation(teamId, location) {
+ if(!this.started) {
+ return false;
+ }
+ let team = this.getTeam(teamId);
+ if(team == undefined) {
+ return false;
+ }
+ team.currentLocation = location;
+ return true;
+ }
+
+ sendLocation(teamId) {
+ if(!this.started) {
+ return false;
+ }
+ let team = this.getTeam(teamId);
+ if(team == undefined) {
+ return false;
+ }
+ team.lastSentLocation = team.currentLocation;
+ team.enemyLocation = this.getTeam(team.chasing).lastSentLocation;
+ return team;
+ }
+
+ removeTeam(teamId) {
+ if(this.getTeam(teamId) == undefined) {
+ return false;
+ }
+ //remove the team from the list
+ this.teams = this.teams.filter(t => t.id !== teamId);
+ this.updateTeamChasing();
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/traque-back/index.js b/traque-back/index.js
new file mode 100644
index 0000000..581b62a
--- /dev/null
+++ b/traque-back/index.js
@@ -0,0 +1,186 @@
+import { createServer } from "http";
+import { Server } from "socket.io";
+import Game from "./game.js";
+
+const httpServer = createServer();
+//Password that socket clients will have to send to be able to send admin commands
+//TODO: put this in an environment variable
+const ADMIN_PASSWORD = "admin";
+
+//set cors to allow all origins
+const io = new Server(httpServer, {
+ cors: {
+ origin: "*",
+ methods: ["GET", "POST"]
+ }
+});
+
+
+/**
+ * Send a message to all logged in sockets
+ * @param {String} event The event name
+ * @param {String} data The data to send
+ */
+function secureBroadcast(event, data) {
+ loggedInSockets.forEach(s => {
+ io.of("admin").to(s).emit(event, data);
+ });
+}
+
+
+const game = new Game();
+
+//Array of logged in sockets
+let loggedInSockets = [];
+
+
+//Admin namespace
+io.of("admin").on("connection", (socket) => {
+ //Flag to check if the user is logged in, defined for each socket
+ let loggedIn = false;
+
+ socket.on("disconnect", () => {
+ console.log("user disconnected");
+ //Remove the socket from the logged in sockets array
+ loggedInSockets = loggedInSockets.filter(s => s !== socket.id);
+ });
+
+ //User is attempting to log in
+ socket.on("login", (password) => {
+ if (password === ADMIN_PASSWORD && !loggedIn) {
+ //Attempt successful
+ socket.emit("login_response", true);
+ loggedInSockets.push(socket.id);
+ loggedIn = true;
+ } else {
+ //Attempt unsuccessful
+ socket.emit("login_response", false);
+ }
+ });
+
+ //User is attempting to add a new team
+ socket.on("add_team", (teamName) => {
+ if(!loggedIn) {
+ socket.emit("error", "Not logged in");
+ return;
+ }
+ if(game.addTeam(teamName)) {
+ secureBroadcast("teams", game.teams);
+ }else {
+ socket.emit("error", "Error adding team");
+ }
+ });
+
+ //User is attempting to remove a team
+ socket.on("remove_team", (teamId) => {
+ if(!loggedIn) {
+ socket.emit("error", "Not logged in");
+ return;
+ }
+ if(game.removeTeam(teamId)) {
+ secureBroadcast("teams", game.teams);
+ }else {
+ socket.emit("error", "Error removing team");
+ }
+ });
+
+ //User is attempting to start the game
+ socket.on("start_game", () => {
+ if(!loggedIn) {
+ socket.emit("error", "Not logged in");
+ return;
+ }
+ if(game.start()) {
+ secureBroadcast("game_started", true);
+ }else {
+ socket.emit("error", "Error starting game");
+ }
+ });
+
+ //User is attempting to stop the game
+ socket.on("stop_game", () => {
+ if(!loggedIn) {
+ socket.emit("error", "Not logged in");
+ return;
+ }
+ if(game.stop()) {
+ secureBroadcast("game_started", false);
+ }else {
+ socket.emit("error", "Error stopping game");
+ }
+ });
+
+ //Use is sending a new list containing the new order of the teams
+ //Note that we never check if the new order contains the same teams as the old order, so it behaves more like a setTeams function
+ //But the frontend should always send the same teams in a different order
+ socket.on("reorder_teams", (newOrder) => {
+ if(!loggedIn) {
+ socket.emit("error", "Not logged in");
+ return;
+ }
+ if(game.reorderTeams(newOrder)) {
+ secureBroadcast("teams", game.teams);
+ } else {
+ socket.emit("error", "Error reordering teams");
+ }
+ });
+
+ //Change the name of a team given its id
+ socket.on("rename_team", (teamId, newName) => {
+ if(!loggedIn) {
+ socket.emit("error", "Not logged in");
+ return;
+ }
+ if(game.renameTeam(teamId, newName)) {
+ secureBroadcast("teams", game.teams);
+ } else {
+ socket.emit("error", "Error renaming team");
+ }
+ });
+
+ //Request an update of the team list
+ //We only reply to the sender to prevent spam
+ socket.on("get_teams", () => {
+ if(!loggedIn) {
+ socket.emit("error", "Not logged in");
+ return;
+ }
+ socket.emit("teams", game.teams);
+ });
+
+});
+
+
+
+io.of("player").on("connection", (socket) => {
+ let teamId = null;
+ console.log("a user connected");
+
+ socket.on("disconnect", () => {
+ console.log("user disconnected");
+ game.getTeam(teamId).sockets = game.getTeam(teamId).sockets.filter(s => s !== socket.id);
+ });
+
+ socket.on("login", (teamId) => {
+ if(game.getTeam(teamId) === undefined) {
+ socket.emit("login_response", false);
+ return;
+ }
+ game.getTeam(teamId).sockets.push(socket.id);
+ socket.emit("login_response", true);
+ });
+
+ socket.on("update_position", (position) => {
+ game.updateLocation(teamId, position);
+ });
+
+ socket.on("send_position", () => {
+ console.log("send_position", position);
+ game.sendLocation(teamId);
+ game.getTeam(teamId).sockets.forEach(s => {
+ io.of("player").to(s).emit("enemy_position", game.getTeam(teamId).enemyLocation);
+ });
+ });
+});
+
+httpServer.listen(3000);
\ No newline at end of file
diff --git a/traque-back/package-lock.json b/traque-back/package-lock.json
new file mode 100644
index 0000000..a1b9260
--- /dev/null
+++ b/traque-back/package-lock.json
@@ -0,0 +1,237 @@
+{
+ "name": "traque-back",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "traque-back",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "socket.io": "^4.7.5"
+ }
+ },
+ "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/@types/cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.17",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
+ "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.11.30",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
+ "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "engines": {
+ "node": "^4.5.0 || >= 5.9"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io": {
+ "version": "6.5.4",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz",
+ "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==",
+ "dependencies": {
+ "@types/cookie": "^0.4.1",
+ "@types/cors": "^2.8.12",
+ "@types/node": ">=10.0.0",
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.4.1",
+ "cors": "~2.8.5",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.11.0"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
+ "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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=="
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/socket.io": {
+ "version": "4.7.5",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
+ "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "cors": "~2.8.5",
+ "debug": "~4.3.2",
+ "engine.io": "~6.5.2",
+ "socket.io-adapter": "~2.5.2",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/socket.io-adapter": {
+ "version": "2.5.4",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz",
+ "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==",
+ "dependencies": {
+ "debug": "~4.3.4",
+ "ws": "~8.11.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/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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
+ }
+ }
+ }
+ }
+}
diff --git a/traque-back/package.json b/traque-back/package.json
index 275fc11..961ebbe 100644
--- a/traque-back/package.json
+++ b/traque-back/package.json
@@ -4,8 +4,13 @@
"description": "",
"main": "index.js",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node index.js"
},
"author": "Quentin Roussel",
- "license": "ISC"
+ "license": "ISC",
+ "type": "module",
+ "dependencies": {
+ "socket.io": "^4.7.5"
+ }
}
diff --git a/traque-front/app/admin/layout.js b/traque-front/app/admin/layout.js
new file mode 100644
index 0000000..c1d7931
--- /dev/null
+++ b/traque-front/app/admin/layout.js
@@ -0,0 +1,12 @@
+import { AdminConnexionProvider } from "@/context/adminConnexionContext";
+import { AdminProvider } from "@/context/adminContext";
+
+export default function AdminLayout({ children}) {
+ return (
+
30min
before penalty
diff --git a/traque-front/components/admin/teamAdd.jsx b/traque-front/components/admin/teamAdd.jsx new file mode 100644 index 0000000..3d5c7fc --- /dev/null +++ b/traque-front/components/admin/teamAdd.jsx @@ -0,0 +1,22 @@ +import React from 'react' +import TextInput from '../util/textInput' +import Button from '../util/button' + +export default function TeamAddForm({onAddTeam}) { + const [teamName, setTeamName] = React.useState(''); + function handleSubmit(e) { + e.preventDefault(); + onAddTeam(teamName); + setTeamName("") + } + return ( + + ) +} diff --git a/traque-front/components/admin/teamEdit.jsx b/traque-front/components/admin/teamEdit.jsx new file mode 100644 index 0000000..e0c5b97 --- /dev/null +++ b/traque-front/components/admin/teamEdit.jsx @@ -0,0 +1,62 @@ +import React, { useEffect, useState } from 'react' +import TextInput from '../util/textInput' +import Button from '../util/button'; +import useAdmin from '@/hook/useAdmin'; + +export default function TeamEdit({selectedTeamId, setSelectedTeamId}) { + const [newTeamName, setNewTeamName] = React.useState(''); + const {setTeamName, getTeamName, removeTeam, getTeam} = useAdmin(); + const [team, setTeam] = useState({}) + + useEffect(() => { + let team = getTeam(selectedTeamId); + if (team != undefined) { + setNewTeamName(team.name); + } + },[selectedTeamId]) + + + useEffect(() => { + let team = getTeam(selectedTeamId); + if (team != undefined) { + setTeam(team); + } + }, [selectedTeamId]) + + + function handleSubmit(e) { + e.preventDefault(); + setTeamName(team.id, newTeamName); + } + + function handleRemove() { + removeTeam(team.id); + setSelectedTeamId(null); + } + + return (team && +Secret : {team.id}
+Name : {team.name}
+Chasing : {getTeamName(team.chasing)}
+Chased by : {getTeamName(team.chased)}
+{team.name}
+