From a709926b60f43e5b8ad4a1639d4fb194bbbb5186 Mon Sep 17 00:00:00 2001 From: Quentin Roussel Date: Thu, 28 Mar 2024 17:40:36 +0100 Subject: [PATCH 1/7] fixed frontend for #10 --- traque-front/context/socketContext.jsx | 2 -- traque-front/context/teamContext.jsx | 10 ++++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/traque-front/context/socketContext.jsx b/traque-front/context/socketContext.jsx index 273c3a5..5bd13cf 100644 --- a/traque-front/context/socketContext.jsx +++ b/traque-front/context/socketContext.jsx @@ -6,8 +6,6 @@ const { io } = require("socket.io-client"); const SOCKET_URL = 'wss://' + process.env.NEXT_PUBLIC_SOCKET_HOST + ':' + process.env.NEXT_PUBLIC_SOCKET_PORT; const USER_SOCKET_URL = SOCKET_URL + "/player"; const ADMIN_SOCKET_URL = SOCKET_URL + "/admin"; -console.log(USER_SOCKET_URL); -console.log(ADMIN_SOCKET_URL); export const teamSocket = io(USER_SOCKET_URL); export const adminSocket = io(ADMIN_SOCKET_URL); diff --git a/traque-front/context/teamContext.jsx b/traque-front/context/teamContext.jsx index 480b28a..0dfa8e1 100644 --- a/traque-front/context/teamContext.jsx +++ b/traque-front/context/teamContext.jsx @@ -8,19 +8,21 @@ import { useTeamConnexion } from "./teamConnexionContext"; const teamContext = createContext() function TeamProvider({children}) { const [enemyPosition, setEnemyPosition] = useState(); - const currentPosition = useLocation(10000); + const [currentPosition, setCurrentPosition] = useState(); + const measuredLocation = useLocation(10000); const {teamSocket} = useSocket(); const {loggedIn} = useTeamConnexion(); useSocketListener(teamSocket, "enemy_position", setEnemyPosition); + useSocketListener(teamSocket, "live_location", setCurrentPosition); //Send the current position to the server when the user is logged in useEffect(() => { - console.log("sending position", currentPosition); + console.log("sending position", measuredLocation); if(loggedIn) { - teamSocket.emit("update_position", currentPosition); + teamSocket.emit("update_position", measuredLocation); } - }, [loggedIn, currentPosition]); + }, [loggedIn, measuredLocation]); const value = useMemo(() => ({enemyPosition, currentPosition}), [enemyPosition, currentPosition]); return ( From ba7abe3b3c13fda23754099f009fbef016e3245c Mon Sep 17 00:00:00 2001 From: Quentin Roussel Date: Thu, 28 Mar 2024 17:49:45 +0100 Subject: [PATCH 2/7] removed zoom animation for initial zoom --- traque-front/components/team/map.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traque-front/components/team/map.jsx b/traque-front/components/team/map.jsx index 15b5132..5e70585 100644 --- a/traque-front/components/team/map.jsx +++ b/traque-front/components/team/map.jsx @@ -15,7 +15,7 @@ function MapPan(props) { useEffect(() => { if(!initialized && props.center) { - map.flyTo(props.center, DEFAULT_ZOOM); + map.flyTo(props.center, DEFAULT_ZOOM, {animate: false}); setInitialized(true) } },[props.center]); From 27e5a6615a58faf24290b67b43fa7e8d27dd766e Mon Sep 17 00:00:00 2001 From: Quentin Roussel Date: Thu, 28 Mar 2024 20:47:05 +0100 Subject: [PATCH 3/7] improved password protection code --- traque-front/app/admin/layout.js | 2 +- traque-front/app/admin/login/page.js | 11 ++--- traque-front/app/admin/page.js | 42 ++++-------------- traque-front/app/admin/teams/page.js | 28 ++++++++++++ traque-front/app/team/layout.js | 3 +- traque-front/app/team/page.js | 12 ++---- traque-front/app/team/track/page.js | 18 +++----- .../context/adminConnexionContext.jsx | 33 +++++--------- traque-front/context/teamConnexionContext.jsx | 28 ++++-------- traque-front/hook/useAdmin.jsx | 4 +- traque-front/hook/useGame.jsx | 5 ++- traque-front/hook/useLocalStorage.jsx | 4 +- traque-front/hook/usePasswordProtect.jsx | 15 +++++++ traque-front/hook/useSocketAuth.jsx | 43 +++++++++++++++++++ 14 files changed, 137 insertions(+), 111 deletions(-) create mode 100644 traque-front/app/admin/teams/page.js create mode 100644 traque-front/hook/usePasswordProtect.jsx create mode 100644 traque-front/hook/useSocketAuth.jsx diff --git a/traque-front/app/admin/layout.js b/traque-front/app/admin/layout.js index c1d7931..2069567 100644 --- a/traque-front/app/admin/layout.js +++ b/traque-front/app/admin/layout.js @@ -1,4 +1,4 @@ -import { AdminConnexionProvider } from "@/context/adminConnexionContext"; +import { AdminConnexionProvider} from "@/context/adminConnexionContext"; import { AdminProvider } from "@/context/adminContext"; export default function AdminLayout({ children}) { diff --git a/traque-front/app/admin/login/page.js b/traque-front/app/admin/login/page.js index 041b393..013d1b9 100644 --- a/traque-front/app/admin/login/page.js +++ b/traque-front/app/admin/login/page.js @@ -1,16 +1,11 @@ "use client"; import LoginForm from '@/components/team/loginForm' import { useAdminConnexion } from '@/context/adminConnexionContext'; -import { redirect } from 'next/navigation'; -import React, { useEffect } from 'react' +import React from 'react' export default function AdminLoginPage() { - const { login, loggedIn } = useAdminConnexion(); - useEffect(() => { - if (loggedIn) { - redirect("/admin"); - } - }, [loggedIn]); + const {login, useProtect} = useAdminConnexion(); + useProtect(); return ( ) diff --git a/traque-front/app/admin/page.js b/traque-front/app/admin/page.js index 1806388..ab3ef39 100644 --- a/traque-front/app/admin/page.js +++ b/traque-front/app/admin/page.js @@ -1,36 +1,12 @@ "use client"; -import TeamAddForm from '@/components/admin/teamAdd'; -import TeamEdit from '@/components/admin/teamEdit'; -import TeamList from '@/components/admin/teamList'; -import { useAdminConnexion } from '@/context/adminConnexionContext'; -import useAdmin from '@/hook/useAdmin'; -import { redirect } from 'next/navigation'; -import React, { useEffect, useState } from 'react' +import { useAdminConnexion } from "@/context/adminConnexionContext"; - -export default function Admin() { - const [selectedTeamId, setSelectedTeamId] = useState(null); - const { loggedIn } = useAdminConnexion(); - const { addTeam } = useAdmin(); - - useEffect(() => { - if (!loggedIn) { - redirect("/admin/login"); - } - }, [loggedIn]); - - - - return ( -
-
-

Team list

- - +export default function AdminPage() { + const { useProtect } = useAdminConnexion(); + useProtect(); + return ( +
+

Admin page

-
- -
-
- ) -} + ) +} \ No newline at end of file diff --git a/traque-front/app/admin/teams/page.js b/traque-front/app/admin/teams/page.js new file mode 100644 index 0000000..e4e00c5 --- /dev/null +++ b/traque-front/app/admin/teams/page.js @@ -0,0 +1,28 @@ +"use client"; +import TeamAddForm from '@/components/admin/teamAdd'; +import TeamEdit from '@/components/admin/teamEdit'; +import TeamList from '@/components/admin/teamList'; +import { useAdminConnexion } from '@/context/adminConnexionContext'; +import useAdmin from '@/hook/useAdmin'; +import React, { useState } from 'react' + +export default function TeamAdminPage() { + const [selectedTeamId, setSelectedTeamId] = useState(null); + const { addTeam } = useAdmin(); + const { useProtect } = useAdminConnexion(); + useProtect(); + + + return ( +
+
+

Team list

+ + +
+
+ +
+
+ ) +} diff --git a/traque-front/app/team/layout.js b/traque-front/app/team/layout.js index f877101..0d97272 100644 --- a/traque-front/app/team/layout.js +++ b/traque-front/app/team/layout.js @@ -1,7 +1,8 @@ +"use client"; import { TeamConnexionProvider } from "@/context/teamConnexionContext"; import { TeamProvider } from "@/context/teamContext"; -export default function AdminLayout({ children}) { +export default function AdminLayout({ children }) { return ( diff --git a/traque-front/app/team/page.js b/traque-front/app/team/page.js index 141267a..f6ab21d 100644 --- a/traque-front/app/team/page.js +++ b/traque-front/app/team/page.js @@ -1,16 +1,10 @@ "use client" import LoginForm from "@/components/team/loginForm"; -import useGame from "@/hook/useGame"; -import { redirect } from "next/navigation"; -import { useEffect } from "react"; +import { useTeamConnexion } from "@/context/teamConnexionContext"; export default function Home() { - const { login, loggedIn } = useGame(); - useEffect(() => { - if (loggedIn) { - redirect("/team/track"); - } - }, [loggedIn]); + const { login,useProtect } = useTeamConnexion(); + useProtect(); return (
login(parseInt(value))}/> diff --git a/traque-front/app/team/track/page.js b/traque-front/app/team/track/page.js index 96478ac..eeb12d4 100644 --- a/traque-front/app/team/track/page.js +++ b/traque-front/app/team/track/page.js @@ -1,10 +1,9 @@ "use client"; import ActionDrawer from '@/components/team/actionDrawer'; -import Button from '@/components/util/button'; +import { useTeamConnexion } from '@/context/teamConnexionContext'; import useGame from '@/hook/useGame'; import dynamic from 'next/dynamic'; -import { redirect } from 'next/navigation'; -import React, { useEffect } from 'react' +import React, { use } from 'react' //Load the map without SSR const LiveMap = dynamic(() => import('@/components/team/map'), { @@ -12,17 +11,12 @@ const LiveMap = dynamic(() => import('@/components/team/map'), { }); export default function Track() { - const { currentPosition, enemyPosition, loggedIn } = useGame(); - useEffect(() => { - if (!loggedIn) { - redirect("/team"); - } - }, [loggedIn]); - - + const { currentPosition, enemyPosition } = useGame(); + const {useProtect} = useTeamConnexion(); + useProtect(); return (
- +
) diff --git a/traque-front/context/adminConnexionContext.jsx b/traque-front/context/adminConnexionContext.jsx index d2949ad..7f8c992 100644 --- a/traque-front/context/adminConnexionContext.jsx +++ b/traque-front/context/adminConnexionContext.jsx @@ -1,38 +1,27 @@ "use client"; -import { createContext, useContext, useEffect, useMemo, useState } from "react"; +import { createContext, useContext, useEffect, useMemo, } from "react"; import { useSocket } from "./socketContext"; -import { useSocketListener } from "@/hook/useSocketListener"; -import { useLocalStorage } from "@/hook/useLocalStorage"; +import { useSocketAuth } from "@/hook/useSocketAuth"; +import { redirect, usePathname } from "next/navigation"; +import { usePasswordProtect } from "@/hook/usePasswordProtect"; -const adminContext = createContext(); +const adminConnexionContext = createContext(); const AdminConnexionProvider = ({ children }) => { - const [loggedIn, setLoggedIn] = useState(false); - const [savedPassword, setSavedPassword] = useLocalStorage("admin_password", null); const { adminSocket } = useSocket(); + const { login, loggedIn, loading } = useSocketAuth(adminSocket, "admin_password"); + const useProtect = () => usePasswordProtect("/admin/login", "/admin", loading, loggedIn); - useEffect(() => { - if (savedPassword && !loggedIn) { - adminSocket.emit("login", savedPassword); - } - }, [savedPassword]); - - function login(password) { - setSavedPassword(password) - } - - useSocketListener(adminSocket, "login_response", setLoggedIn); - - const value = useMemo(() => ({ login, loggedIn }), [loggedIn]); + const value = useMemo(() => ({ login, loggedIn, loading, useProtect }), [loggedIn, loading]); return ( - + {children} - + ); } function useAdminConnexion() { - return useContext(adminContext); + return useContext(adminConnexionContext); } export { AdminConnexionProvider, useAdminConnexion }; diff --git a/traque-front/context/teamConnexionContext.jsx b/traque-front/context/teamConnexionContext.jsx index 68c2e27..9b82f24 100644 --- a/traque-front/context/teamConnexionContext.jsx +++ b/traque-front/context/teamConnexionContext.jsx @@ -1,32 +1,20 @@ "use client"; -import { createContext, useContext, useEffect, useMemo, useState } from "react"; +import { createContext, useContext, useMemo } from "react"; import { useSocket } from "./socketContext"; -import { useSocketListener } from "@/hook/useSocketListener"; -import { useLocalStorage } from "@/hook/useLocalStorage"; +import { useSocketAuth } from "@/hook/useSocketAuth"; +import { usePasswordProtect } from "@/hook/usePasswordProtect"; const teamConnexionContext = createContext(); const TeamConnexionProvider = ({ children }) => { - const [loggedIn, setLoggedIn] = useState(false); - const [teamId, setTeamId] = useLocalStorage("team_id", null); const { teamSocket } = useSocket(); + const { login, password: teamId, loggedIn, loading } = useSocketAuth(teamSocket, "team_password"); + const useProtect = () => usePasswordProtect("/team", "/team/track", loading, loggedIn); - useEffect(() => { - if (teamId && !loggedIn) { - teamSocket.emit("login", teamId); - } - }, [teamId]); + const value = useMemo(() => ({ teamId, login, loggedIn, loading, useProtect}), [teamId, login, loggedIn, loading]); - function login(id) { - setTeamId(id); - } - - useSocketListener(teamSocket, "login_response", setLoggedIn); - - const value = useMemo(() => ({ teamId, login, loggedIn }), [teamId, login, loggedIn]); - return ( - {children} + {children} ); } @@ -35,5 +23,5 @@ function useTeamConnexion() { return useContext(teamConnexionContext); } -export { TeamConnexionProvider, useTeamConnexion}; +export { TeamConnexionProvider, useTeamConnexion }; diff --git a/traque-front/hook/useAdmin.jsx b/traque-front/hook/useAdmin.jsx index 45e9ede..991bb8f 100644 --- a/traque-front/hook/useAdmin.jsx +++ b/traque-front/hook/useAdmin.jsx @@ -2,7 +2,7 @@ import { useAdminContext } from "@/context/adminContext"; import { useSocket } from "@/context/socketContext"; export default function useAdmin(){ - const {teams, started} = useAdminContext(); + const {teams, started } = useAdminContext(); const {adminSocket} = useSocket(); function pollTeams() { @@ -42,6 +42,6 @@ export default function useAdmin(){ adminSocket.emit("stop_game"); } - return { teams, started, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, startGame, stopGame, setTeamName }; + return {teams, started, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, startGame, stopGame, setTeamName }; } \ No newline at end of file diff --git a/traque-front/hook/useGame.jsx b/traque-front/hook/useGame.jsx index b9e200b..d255dd1 100644 --- a/traque-front/hook/useGame.jsx +++ b/traque-front/hook/useGame.jsx @@ -6,12 +6,13 @@ import { useTeamContext } from "@/context/teamContext"; export default function useGame() { const {teamSocket} = useSocket(); - const {loggedIn, login, teamId} = useTeamConnexion(); + const {teamId} = useTeamConnexion(); const {currentPosition, enemyPosition} = useTeamContext(); function sendCurrentPosition() { teamSocket.emit("send_position"); } - return { sendCurrentPosition, login, enemyPosition, currentPosition, loggedIn, teamId }; + + return { sendCurrentPosition, enemyPosition, currentPosition, teamId }; } \ No newline at end of file diff --git a/traque-front/hook/useLocalStorage.jsx b/traque-front/hook/useLocalStorage.jsx index 103da22..14a63ce 100644 --- a/traque-front/hook/useLocalStorage.jsx +++ b/traque-front/hook/useLocalStorage.jsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; export function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(initialValue); + const [loading, setLoading] = useState(true); useEffect(() => { try { @@ -11,6 +12,7 @@ export function useLocalStorage(key, initialValue) { } catch (error) { console.log(error); } + setLoading(false); }, []); const setValue = value => { @@ -23,5 +25,5 @@ export function useLocalStorage(key, initialValue) { } } - return [storedValue, setValue]; + return [storedValue, setValue, loading]; } \ No newline at end of file diff --git a/traque-front/hook/usePasswordProtect.jsx b/traque-front/hook/usePasswordProtect.jsx new file mode 100644 index 0000000..581bce4 --- /dev/null +++ b/traque-front/hook/usePasswordProtect.jsx @@ -0,0 +1,15 @@ +"use client"; +import { redirect, usePathname } from "next/navigation"; +import { useEffect } from "react"; + +export function usePasswordProtect(loginPath, redirectPath, loading, loggedIn) { + const path = usePathname(); + useEffect(() => { + if (!loggedIn && !loading && path !== loginPath) { + redirect(loginPath); + } + if(loggedIn && !loading && path === loginPath) { + redirect(redirectPath) + } + }, [loggedIn, loading, path]); +} \ No newline at end of file diff --git a/traque-front/hook/useSocketAuth.jsx b/traque-front/hook/useSocketAuth.jsx new file mode 100644 index 0000000..7f4bfdb --- /dev/null +++ b/traque-front/hook/useSocketAuth.jsx @@ -0,0 +1,43 @@ +import {useEffect, useState} from 'react'; +import { useSocketListener } from './useSocketListener'; +import { useLocalStorage } from './useLocalStorage'; +import { usePathname } from 'next/navigation'; + +const LOGIN_MESSAGE = "login"; +const LOGIN_RESPONSE_MESSAGE = "login_response"; + +export function useSocketAuth(socket, passwordName) { + const [loggedIn, setLoggedIn] = useState(false); + const [loading, setLoading] = useState(true); + const [waitingForResponse, setWaitingForResponse] = useState(true); + const [savedPassword, setSavedPassword, savedPasswordLoading] = useLocalStorage(passwordName, null); + + useEffect(() => { + if (savedPassword && !loggedIn) { + socket.emit(LOGIN_MESSAGE, savedPassword); + } + }, [savedPassword]); + + function login(password) { + setSavedPassword(password) + } + + useSocketListener(socket, LOGIN_RESPONSE_MESSAGE,(loginResponse) => { + setWaitingForResponse(false); + setLoggedIn(loginResponse); + }); + + useEffect(() => { + //There is a password saved and we recieved a login response + if(savedPassword && !waitingForResponse && !savedPasswordLoading) { + setLoading(false); + } + //We tried to load the saved password but it is not there + else if (savedPassword == null && !savedPasswordLoading) { + setLoading(false); + } + }, [waitingForResponse, savedPasswordLoading, savedPassword]); + + + return {login,password: savedPassword, loggedIn, loading}; +} \ No newline at end of file From 1a5405b4e8511eefb001b2462b24afcb30520e52 Mon Sep 17 00:00:00 2001 From: Quentin Roussel Date: Thu, 28 Mar 2024 21:53:43 +0100 Subject: [PATCH 4/7] starting zone element done --- traque-front/app/team/track/page.js | 2 +- traque-front/components/admin/mapPicker.jsx | 70 +++++++++++++++++++++ traque-front/components/admin/teamEdit.jsx | 64 +++++++++++-------- traque-front/hook/useLocation.jsx | 5 +- 4 files changed, 113 insertions(+), 28 deletions(-) create mode 100644 traque-front/components/admin/mapPicker.jsx diff --git a/traque-front/app/team/track/page.js b/traque-front/app/team/track/page.js index eeb12d4..7df6a01 100644 --- a/traque-front/app/team/track/page.js +++ b/traque-front/app/team/track/page.js @@ -3,7 +3,7 @@ import ActionDrawer from '@/components/team/actionDrawer'; import { useTeamConnexion } from '@/context/teamConnexionContext'; import useGame from '@/hook/useGame'; import dynamic from 'next/dynamic'; -import React, { use } from 'react' +import React from 'react' //Load the map without SSR const LiveMap = dynamic(() => import('@/components/team/map'), { diff --git a/traque-front/components/admin/mapPicker.jsx b/traque-front/components/admin/mapPicker.jsx new file mode 100644 index 0000000..fc6fa37 --- /dev/null +++ b/traque-front/components/admin/mapPicker.jsx @@ -0,0 +1,70 @@ +"use client"; +import { useLocation } from "@/hook/useLocation"; +import { use, useEffect, useState } from "react"; +import "leaflet/dist/leaflet.css"; +import { Circle, MapContainer, TileLayer, useMap } from "react-leaflet"; + +function MapPan(props) { + const map = useMap(); + const [initialized, setInitialized] = useState(false); + + useEffect(() => { + if (!initialized && props.center) { + map.flyTo(props.center, props.zoom, { animate: false }); + setInitialized(true) + } + }, [props.center]); + return null; +} + +function MapEventListener({onClick, onMouseMove}) { + const map = useMap(); + useEffect(() => { + map.on('click', onClick); + return () => { + map.off('click', onClick); + } + }, [onClick]); + useEffect(() => { + map.on('mousemove', onMouseMove); + return () => { + map.off('mousemove', onMouseMove); + } + }); + return null; +} + +export function CircularAreaPicker({area, setArea, ...props}) { + const DEFAULT_ZOOM = 17; + const location = useLocation(Infinity); + const [drawing, setDrawing] = useState(false); + const [center, setCenter] = useState(area?.center || null); + const [radius, setRadius] = useState(area?.radius || null); + + function handleClick(e) { + if(!drawing) { + setCenter(e.latlng); + setRadius(null); + setDrawing(true); + } else { + setDrawing(false); + setArea({center, radius}); + } + } + + function handleMouseMove(e) { + if(drawing) { + setRadius(e.latlng.distanceTo(center)); + } + } + return ( + + + {center && radius && } + + + ) +} \ No newline at end of file diff --git a/traque-front/components/admin/teamEdit.jsx b/traque-front/components/admin/teamEdit.jsx index fdc6d2c..8d6eaa8 100644 --- a/traque-front/components/admin/teamEdit.jsx +++ b/traque-front/components/admin/teamEdit.jsx @@ -2,10 +2,15 @@ import React, { useEffect, useState } from 'react' import TextInput from '../util/textInput' import Button from '../util/button'; import useAdmin from '@/hook/useAdmin'; +import dynamic from 'next/dynamic'; -export default function TeamEdit({selectedTeamId, setSelectedTeamId}) { +const CircularAreaPicker = dynamic(() => import('./mapPicker').then((mod) => mod.CircularAreaPicker), { + ssr: false +}); + +export default function TeamEdit({ selectedTeamId, setSelectedTeamId }) { const [newTeamName, setNewTeamName] = React.useState(''); - const {setTeamName, getTeamName, removeTeam, getTeam} = useAdmin(); + const { setTeamName, getTeamName, removeTeam, getTeam } = useAdmin(); const [team, setTeam] = useState({}) useEffect(() => { @@ -13,9 +18,9 @@ export default function TeamEdit({selectedTeamId, setSelectedTeamId}) { if (team != undefined) { setNewTeamName(team.name); } - },[selectedTeamId]) + }, [selectedTeamId]) + - useEffect(() => { let team = getTeam(selectedTeamId); if (team != undefined) { @@ -34,30 +39,37 @@ export default function TeamEdit({selectedTeamId, setSelectedTeamId}) { setSelectedTeamId(null); } - return (team && -
-
-

Actions

-
-
- setNewTeamName(e.target.value)}/> + return (team && +
+ +
+
+

Actions

+ +
+ setNewTeamName(e.target.value)} /> +
+
+ +
+ +
-
- +
+

Team details

+
+

Secret : {String(team.id).padStart(6, '0')}

+

Name : {team.name}

+

Chasing : {getTeamName(team.chasing)}

+

Chased by : {getTeamName(team.chased)}

+

Capture code : {String(team.captureCode).padStart(4, '0')}

+
- - -
-
-

Team details

-
-

Secret : {String(team.id).padStart(6,'0')}

-

Name : {team.name}

-

Chasing : {getTeamName(team.chasing)}

-

Chased by : {getTeamName(team.chased)}

-

Capture code : {String(team.captureCode).padStart(4,'0')}

+
+
+

Starting area

+ setTeam({ ...team, startingArea })} />
-
- ) + ) } diff --git a/traque-front/hook/useLocation.jsx b/traque-front/hook/useLocation.jsx index 70e8c17..37d1272 100644 --- a/traque-front/hook/useLocation.jsx +++ b/traque-front/hook/useLocation.jsx @@ -9,9 +9,12 @@ export function useLocation(interval) { const [location, setLocation] = useState(); useEffect(() => { function update() { + console.log('Updating location'); navigator.geolocation.getCurrentPosition((position) => { setLocation([position.coords.latitude, position.coords.longitude]); - setTimeout(update, interval); + if(interval != Infinity) { + setTimeout(update, interval); + } }, () => { }, { enableHighAccuracy: true, timeout: Infinity, maximumAge: 0 }); } update(); From 0aa42f26d2729f16dd26ec49261e18281d44ac11 Mon Sep 17 00:00:00 2001 From: Quentin Roussel Date: Thu, 28 Mar 2024 22:48:55 +0100 Subject: [PATCH 5/7] implemented backend for storing starting area --- traque-front/components/admin/mapPicker.jsx | 7 +++++++ traque-front/components/admin/teamEdit.jsx | 22 +++++++-------------- traque-front/hook/useAdmin.jsx | 6 +++--- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/traque-front/components/admin/mapPicker.jsx b/traque-front/components/admin/mapPicker.jsx index fc6fa37..f6aa757 100644 --- a/traque-front/components/admin/mapPicker.jsx +++ b/traque-front/components/admin/mapPicker.jsx @@ -41,6 +41,13 @@ export function CircularAreaPicker({area, setArea, ...props}) { const [center, setCenter] = useState(area?.center || null); const [radius, setRadius] = useState(area?.radius || null); + useEffect(() => { + console.log(area) + setDrawing(false); + setCenter(area?.center || null); + setRadius(area?.radius || null); + }, [area]) + function handleClick(e) { if(!drawing) { setCenter(e.latlng); diff --git a/traque-front/components/admin/teamEdit.jsx b/traque-front/components/admin/teamEdit.jsx index 8d6eaa8..6c09fd7 100644 --- a/traque-front/components/admin/teamEdit.jsx +++ b/traque-front/components/admin/teamEdit.jsx @@ -10,28 +10,20 @@ const CircularAreaPicker = dynamic(() => import('./mapPicker').then((mod) => mod export default function TeamEdit({ selectedTeamId, setSelectedTeamId }) { const [newTeamName, setNewTeamName] = React.useState(''); - const { setTeamName, getTeamName, removeTeam, getTeam } = useAdmin(); + const { updateTeam, getTeamName, removeTeam, getTeam, teams } = 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]) + }, [selectedTeamId, teams]) - - function handleSubmit(e) { + function handleRename(e) { e.preventDefault(); - setTeamName(team.id, newTeamName); + updateTeam(team.id, {name:newTeamName}); } function handleRemove() { @@ -45,7 +37,7 @@ export default function TeamEdit({ selectedTeamId, setSelectedTeamId }) {

Actions

-
+
setNewTeamName(e.target.value)} />
@@ -53,7 +45,7 @@ export default function TeamEdit({ selectedTeamId, setSelectedTeamId }) {
- +

Team details

@@ -68,7 +60,7 @@ export default function TeamEdit({ selectedTeamId, setSelectedTeamId }) {

Starting area

- setTeam({ ...team, startingArea })} /> + updateTeam(team.id, {startingArea})} />
) diff --git a/traque-front/hook/useAdmin.jsx b/traque-front/hook/useAdmin.jsx index 991bb8f..ef7c693 100644 --- a/traque-front/hook/useAdmin.jsx +++ b/traque-front/hook/useAdmin.jsx @@ -30,8 +30,8 @@ export default function useAdmin(){ adminSocket.emit("remove_team", teamId); } - function setTeamName(teamId, newName) { - adminSocket.emit("rename_team", teamId, newName); + function updateTeam(teamId, team) { + adminSocket.emit("update_team", teamId, team); } function startGame() { @@ -42,6 +42,6 @@ export default function useAdmin(){ adminSocket.emit("stop_game"); } - return {teams, started, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, startGame, stopGame, setTeamName }; + return {teams, started, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, startGame, stopGame, updateTeam }; } \ No newline at end of file From ba2a4a0ea1b781c29c75f347f258370113915b8a Mon Sep 17 00:00:00 2001 From: Quentin Roussel Date: Thu, 28 Mar 2024 23:45:53 +0100 Subject: [PATCH 6/7] added navbar to admin interface --- traque-front/app/admin/layout.js | 15 ++++++++++++++- traque-front/app/admin/teams/page.js | 8 ++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/traque-front/app/admin/layout.js b/traque-front/app/admin/layout.js index 2069567..0463940 100644 --- a/traque-front/app/admin/layout.js +++ b/traque-front/app/admin/layout.js @@ -1,11 +1,24 @@ import { AdminConnexionProvider} from "@/context/adminConnexionContext"; import { AdminProvider } from "@/context/adminContext"; +import Link from "next/link"; export default function AdminLayout({ children}) { return ( - {children} +
+
+
Admin
+
    +
  • Home
  • +
  • Teams
  • +
  • Map
  • +
+
+
+ {children} +
+
) diff --git a/traque-front/app/admin/teams/page.js b/traque-front/app/admin/teams/page.js index e4e00c5..0b6db60 100644 --- a/traque-front/app/admin/teams/page.js +++ b/traque-front/app/admin/teams/page.js @@ -14,14 +14,14 @@ export default function TeamAdminPage() { return ( -
-
+
+

Team list

-
- +
+ {selectedTeamId && }
) From ae2587bc3a2ca50bffabf947c37798e39d1bf630 Mon Sep 17 00:00:00 2001 From: Quentin Roussel Date: Fri, 29 Mar 2024 00:33:40 +0100 Subject: [PATCH 7/7] implemented game state in front end --- traque-front/app/admin/page.js | 14 ++++++++++++-- traque-front/app/team/track/page.js | 8 ++++---- traque-front/components/admin/teamAdd.jsx | 4 ++-- traque-front/components/admin/teamEdit.jsx | 6 +++--- traque-front/components/team/actionDrawer.jsx | 6 +++--- traque-front/components/team/loginForm.jsx | 4 ++-- traque-front/components/team/map.jsx | 5 +++-- traque-front/components/util/button.jsx | 2 +- traque-front/context/adminConnexionContext.jsx | 3 +-- traque-front/context/adminContext.jsx | 7 ++++--- traque-front/context/teamContext.jsx | 5 ++++- traque-front/hook/useAdmin.jsx | 12 ++++-------- traque-front/hook/useGame.jsx | 4 ++-- traque-front/util/gameState.js | 6 ++++++ 14 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 traque-front/util/gameState.js diff --git a/traque-front/app/admin/page.js b/traque-front/app/admin/page.js index ab3ef39..c8c7e5d 100644 --- a/traque-front/app/admin/page.js +++ b/traque-front/app/admin/page.js @@ -1,12 +1,22 @@ "use client"; +import BlueButton, { GreenButton, RedButton } from "@/components/util/button"; import { useAdminConnexion } from "@/context/adminConnexionContext"; +import useAdmin from "@/hook/useAdmin"; +import { GameState } from "@/util/gameState"; export default function AdminPage() { const { useProtect } = useAdminConnexion(); + const { gameState, changeState } = useAdmin(); useProtect(); return ( -
-

Admin page

+
+
+

Game state

+ Current : {gameState} + changeState(GameState.SETUP)}>Reset game + changeState(GameState.PLACEMENT)}>Start placement + changeState(GameState.PLAYING)}>Start game +
) } \ No newline at end of file diff --git a/traque-front/app/team/track/page.js b/traque-front/app/team/track/page.js index 7df6a01..280aeec 100644 --- a/traque-front/app/team/track/page.js +++ b/traque-front/app/team/track/page.js @@ -1,7 +1,7 @@ "use client"; import ActionDrawer from '@/components/team/actionDrawer'; import { useTeamConnexion } from '@/context/teamConnexionContext'; -import useGame from '@/hook/useGame'; +import useGame, { GameState } from '@/hook/useGame'; import dynamic from 'next/dynamic'; import React from 'react' @@ -11,11 +11,11 @@ const LiveMap = dynamic(() => import('@/components/team/map'), { }); export default function Track() { - const { currentPosition, enemyPosition } = useGame(); + const { gameState } = useGame(); const {useProtect} = useTeamConnexion(); useProtect(); - return ( -
+ return ( + gameState == GameState.PLAYING &&
diff --git a/traque-front/components/admin/teamAdd.jsx b/traque-front/components/admin/teamAdd.jsx index 3d5c7fc..35c44ab 100644 --- a/traque-front/components/admin/teamAdd.jsx +++ b/traque-front/components/admin/teamAdd.jsx @@ -1,6 +1,6 @@ import React from 'react' import TextInput from '../util/textInput' -import Button from '../util/button' +import BlueButton from '../util/button' export default function TeamAddForm({onAddTeam}) { const [teamName, setTeamName] = React.useState(''); @@ -15,7 +15,7 @@ export default function TeamAddForm({onAddTeam}) { setTeamName(e.target.value)}/>
- + +
) diff --git a/traque-front/components/admin/teamEdit.jsx b/traque-front/components/admin/teamEdit.jsx index 6c09fd7..3af1d96 100644 --- a/traque-front/components/admin/teamEdit.jsx +++ b/traque-front/components/admin/teamEdit.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react' import TextInput from '../util/textInput' -import Button from '../util/button'; +import BlueButton from '../util/button'; import useAdmin from '@/hook/useAdmin'; import dynamic from 'next/dynamic'; @@ -42,10 +42,10 @@ export default function TeamEdit({ selectedTeamId, setSelectedTeamId }) { setNewTeamName(e.target.value)} />
- + Rename
- + Remove

Team details

diff --git a/traque-front/components/team/actionDrawer.jsx b/traque-front/components/team/actionDrawer.jsx index 028aee1..f9e095e 100644 --- a/traque-front/components/team/actionDrawer.jsx +++ b/traque-front/components/team/actionDrawer.jsx @@ -1,6 +1,6 @@ import useGame from "@/hook/useGame"; import { useState } from "react" -import Button, { GreenButton, RedButton } from "../util/button"; +import BlueButton, { GreenButton, RedButton } from "../util/button"; import TextInput from "../util/textInput"; export default function ActionDrawer() { @@ -23,10 +23,10 @@ export default function ActionDrawer() {
- + Update position
- + Message log
See target info diff --git a/traque-front/components/team/loginForm.jsx b/traque-front/components/team/loginForm.jsx index 044bdbc..b5e76c9 100644 --- a/traque-front/components/team/loginForm.jsx +++ b/traque-front/components/team/loginForm.jsx @@ -1,6 +1,6 @@ "use client"; import { useState } from "react"; -import Button from "../util/button"; +import BlueButton from "../util/button"; import TextInput from "../util/textInput"; export default function LoginForm({ onSubmit, title, placeholder, buttonText}) { @@ -14,7 +14,7 @@ export default function LoginForm({ onSubmit, title, placeholder, buttonText}) {

{title}

setValue(e.target.value)} name="team-id"/> - + {buttonText} ) } \ No newline at end of file diff --git a/traque-front/components/team/map.jsx b/traque-front/components/team/map.jsx index 5e70585..7d9a426 100644 --- a/traque-front/components/team/map.jsx +++ b/traque-front/components/team/map.jsx @@ -4,6 +4,7 @@ import { MapContainer, Marker, Popup, TileLayer, useMap } from 'react-leaflet' import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css' import "leaflet-defaulticon-compatibility"; import "leaflet/dist/leaflet.css"; +import useGame from '@/hook/useGame'; const DEFAULT_ZOOM = 17; @@ -23,8 +24,8 @@ function MapPan(props) { return null; } -export default function LiveMap({enemyPosition, currentPosition, ...props}) { - +export default function LiveMap({ ...props}) { + const {currentPosition, enemyPosition} = useGame(); return ( {children} ) diff --git a/traque-front/context/adminConnexionContext.jsx b/traque-front/context/adminConnexionContext.jsx index 7f8c992..52dad56 100644 --- a/traque-front/context/adminConnexionContext.jsx +++ b/traque-front/context/adminConnexionContext.jsx @@ -1,8 +1,7 @@ "use client"; -import { createContext, useContext, useEffect, useMemo, } from "react"; +import { createContext, useContext, useMemo, } from "react"; import { useSocket } from "./socketContext"; import { useSocketAuth } from "@/hook/useSocketAuth"; -import { redirect, usePathname } from "next/navigation"; import { usePasswordProtect } from "@/hook/usePasswordProtect"; const adminConnexionContext = createContext(); diff --git a/traque-front/context/adminContext.jsx b/traque-front/context/adminContext.jsx index f4dfc48..0f3cdd4 100644 --- a/traque-front/context/adminContext.jsx +++ b/traque-front/context/adminContext.jsx @@ -3,15 +3,17 @@ import { createContext, useContext, useEffect, useMemo, useState } from "react"; import { useSocket } from "./socketContext"; import { useSocketListener } from "@/hook/useSocketListener"; import { useAdminConnexion } from "./adminConnexionContext"; +import { GameState } from "@/util/gameState"; const adminContext = createContext(); function AdminProvider({children}) { const [teams, setTeams] = useState([]); - const [started, setStarted] = useState(false); const { adminSocket } = useSocket(); const {loggedIn} = useAdminConnexion(); + const [gameState, setGameState] = useState(GameState.SETUP); + useSocketListener(adminSocket, "game_state", setGameState); //Send a request to get the teams when the user logs in useEffect(() => { adminSocket.emit("get_teams"); @@ -19,9 +21,8 @@ function AdminProvider({children}) { //Bind listeners to update the team list and the game status on socket message useSocketListener(adminSocket, "teams", setTeams); - useSocketListener(adminSocket, "game_started", setStarted); - const value = useMemo(() => ({teams, setTeams, started, setStarted}), [teams, started]); + const value = useMemo(() => ({teams, setTeams, gameState}), [teams, gameState]); return ( {children} diff --git a/traque-front/context/teamContext.jsx b/traque-front/context/teamContext.jsx index 0dfa8e1..f7b056d 100644 --- a/traque-front/context/teamContext.jsx +++ b/traque-front/context/teamContext.jsx @@ -5,14 +5,17 @@ import { createContext, useContext, useEffect, useMemo, useState } from "react"; import { useSocket } from "./socketContext"; import { useTeamConnexion } from "./teamConnexionContext"; + const teamContext = createContext() function TeamProvider({children}) { const [enemyPosition, setEnemyPosition] = useState(); const [currentPosition, setCurrentPosition] = useState(); + const [gameState, setGameState] = useState(GameState.SETUP); const measuredLocation = useLocation(10000); const {teamSocket} = useSocket(); const {loggedIn} = useTeamConnexion(); + useSocketListener(teamSocket, "game_state", setGameState); useSocketListener(teamSocket, "enemy_position", setEnemyPosition); useSocketListener(teamSocket, "live_location", setCurrentPosition); @@ -24,7 +27,7 @@ function TeamProvider({children}) { } }, [loggedIn, measuredLocation]); - const value = useMemo(() => ({enemyPosition, currentPosition}), [enemyPosition, currentPosition]); + const value = useMemo(() => ({enemyPosition, currentPosition, gameState}), [enemyPosition, currentPosition, gameState]); return ( {children} diff --git a/traque-front/hook/useAdmin.jsx b/traque-front/hook/useAdmin.jsx index ef7c693..56c566e 100644 --- a/traque-front/hook/useAdmin.jsx +++ b/traque-front/hook/useAdmin.jsx @@ -2,7 +2,7 @@ import { useAdminContext } from "@/context/adminContext"; import { useSocket } from "@/context/socketContext"; export default function useAdmin(){ - const {teams, started } = useAdminContext(); + const {teams, gameState } = useAdminContext(); const {adminSocket} = useSocket(); function pollTeams() { @@ -34,14 +34,10 @@ export default function useAdmin(){ adminSocket.emit("update_team", teamId, team); } - function startGame() { - adminSocket.emit("start_game"); + function changeState(state) { + adminSocket.emit("change_state", state); } - function stopGame() { - adminSocket.emit("stop_game"); - } - - return {teams, started, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, startGame, stopGame, updateTeam }; + return {teams, gameState, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, changeState, updateTeam }; } \ No newline at end of file diff --git a/traque-front/hook/useGame.jsx b/traque-front/hook/useGame.jsx index d255dd1..7c7bfec 100644 --- a/traque-front/hook/useGame.jsx +++ b/traque-front/hook/useGame.jsx @@ -7,12 +7,12 @@ import { useTeamContext } from "@/context/teamContext"; export default function useGame() { const {teamSocket} = useSocket(); const {teamId} = useTeamConnexion(); - const {currentPosition, enemyPosition} = useTeamContext(); + const {currentPosition, enemyPosition, gameState} = useTeamContext(); function sendCurrentPosition() { teamSocket.emit("send_position"); } - return { sendCurrentPosition, enemyPosition, currentPosition, teamId }; + return { sendCurrentPosition, enemyPosition, currentPosition, teamId, gameState}; } \ No newline at end of file diff --git a/traque-front/util/gameState.js b/traque-front/util/gameState.js new file mode 100644 index 0000000..e5514f7 --- /dev/null +++ b/traque-front/util/gameState.js @@ -0,0 +1,6 @@ +export const GameState = { + SETUP: "setup", + PLACEMENT: "placement", + PLAYING: "playing", + FINISHED: "finished" +} \ No newline at end of file