From 5032b3c7d1d19fd4ee4b0d22e77b0cad67e17093 Mon Sep 17 00:00:00 2001 From: Aidan Haas Date: Thu, 10 Jul 2025 14:49:49 -0400 Subject: [PATCH] polished homepage, added game info table --- README.md | 12 +- backend/server.py | 20 ++- frontend/app/page.tsx | 278 +++++++++++++++++++++----------- frontend/public/add.svg | 7 + frontend/public/next.svg | 8 +- frontend/public/play-button.svg | 7 + frontend/public/plus.svg | 7 + frontend/public/reset.svg | 2 + 8 files changed, 238 insertions(+), 103 deletions(-) create mode 100644 frontend/public/add.svg create mode 100644 frontend/public/play-button.svg create mode 100644 frontend/public/plus.svg create mode 100644 frontend/public/reset.svg diff --git a/README.md b/README.md index 11d0a2f..696056b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,14 @@ # Pool Tracker Simple web app the keeps track of who's turn it is in pool. -Add the players with the homepage, and view who's turn it is on the `/live` page \ No newline at end of file +Uses Next.JS for frontend and Flask for the backend. + +Syncs in realtime between devices with sockets + +Add the players with the homepage, and view who's turn it is on the `/live` page + + + +Tools used: +SVGs generated from https://www.svgrepo.com +https://www.freetool.dev/emoji-picker/ \ No newline at end of file diff --git a/backend/server.py b/backend/server.py index 43b16aa..dea552d 100644 --- a/backend/server.py +++ b/backend/server.py @@ -7,7 +7,7 @@ from flask_cors import CORS from flask_socketio import SocketIO app = Flask(__name__) -CORS(app, origins=["http://localhost:3000", "10.*"]) +CORS(app, origins=["*", "0.0.0.0"]) socketio = SocketIO(app, cors_allowed_origins="*") @@ -38,11 +38,10 @@ def add_player(): @app.route("/reset", methods=["GET"]) def reset(): - game_state = { - "gameActive": False, - "players": [""], - "playerTurn": 0 - } + game_state["gameActive"] = False + game_state["players"] = [] + game_state["playerTurn"] = 0 + socketio.emit("player_update", {"players": game_state["players"]}) return jsonify(game_state) @@ -50,6 +49,13 @@ def reset(): def status(): return jsonify(game_state) +@app.route("/start", methods=["GET"]) +def start_game(): + game_state["gameActive"] = True + socketio.emit("player_update", {"players": game_state["players"]}) + return jsonify(game_state) + + @app.route("/next", methods=["GET"]) def next_player(): players = game_state["players"] @@ -67,4 +73,4 @@ def next_player(): }) if __name__ == "__main__": - socketio.run(app, host="localhost", port=8080) \ No newline at end of file + socketio.run(app, host="0.0.0.0", port=8080) \ No newline at end of file diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 2336be7..d1cec22 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -1,112 +1,202 @@ 'use client'; import Image from "next/image"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; +import io from 'socket.io-client'; export default function Home() { -const nameInputRef = useRef(null); -const currentPlayers = useState(null); + const nameInputRef = useRef(null); + const currentPlayers = useState(null); + const [gameStatus, setGameStatus] = useState<{ gameActive: boolean, players: any[] }>({ + gameActive: false, + players: [], + }); -const addPlayer = async () => { - const playerName = nameInputRef.current?.value?.trim(); - if (!playerName) { - alert("Please enter a player name"); - return; - } + let baseUrl = ""; + if (typeof window !== "undefined") { + baseUrl = `${window.location.protocol}//${window.location.hostname}`; + } + const backendUrl = `${baseUrl}:${process.env.NEXT_PUBLIC_API_BASE}`; + const socket = io(`${backendUrl}`); + + const fetchStatus = async () => { try { - console.log(`${process.env.NEXT_PUBLIC_API_BASE}`); - const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE}/add`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name: playerName, group: selectedGroup }), - }); - - if (!res.ok) { - throw new Error(`Server error: ${res.statusText}`); - } - - if (nameInputRef.current) { - nameInputRef.current.value = ""; - } - } catch (error) { - console.error("Failed to add player:", error); + const res = await fetch(`${backendUrl}/status`); + const data = await res.json(); + setGameStatus(data); + } catch (err) { + console.error("Failed to fetch status", err); } }; - const [selectedGroup, setSelectedGroup] = useState<"stripes" | "solids">("stripes"); - const advanceTurn = async () => { - await fetch('http://localhost:8080/next'); // triggers backend to emit event - }; + // useEffect(() => { + // fetchStatus(); + // // Need to change this so it just listens on a socket instead + // const interval = setInterval(fetchStatus, 5000); + // return () => clearInterval(interval); + // }, []); - const resetGame = async () => { - await fetch('http://localhost:8080/reset'); // triggers backend to emit event - }; - - return ( -
- Game Info + useEffect(() => { + socket.on('connect', () => { + console.log('Connected to Socket.IO server'); + fetchStatus(); + }); -
-
- + socket.on('player_update', (data: { nextPlayer: string }) => { + fetchStatus(); + }); - + return () => { + socket.off('player_update'); + }; + }, []); + + const addPlayer = async () => { + const playerName = nameInputRef.current?.value?.trim(); + if (!playerName) { + alert("Please enter a player name"); + return; + } + + try { + console.log(`${process.env.NEXT_PUBLIC_API_BASE}`); + const res = await fetch(`${backendUrl}/add`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: playerName, group: selectedGroup }), + }); + + if (!res.ok) { + throw new Error(`Server error: ${res.statusText}`); + } + + if (nameInputRef.current) { + nameInputRef.current.value = ""; + } + } catch (error) { + console.error("Failed to add player:", error); + } + }; + + const startGame = async() => { + fetch(`${backendUrl}/start`); + } + + const [selectedGroup, setSelectedGroup] = useState<"stripes" | "solids">("stripes"); + + const advanceTurn = async () => { + await fetch(`${backendUrl}/next`); // triggers backend to emit event + }; + + const resetGame = async () => { + await fetch(`${backendUrl}/reset`); // triggers backend to emit event + }; + + return ( + +
+
+

🎯 Game Info

+

Game Active: {gameStatus.gameActive ? "Yes" : "No"}

+ + + + + + {gameStatus.players.map((player, idx) => ( + + + + + ))} + +
PlayerGroup
{player.name}{player.group}
-
- - -
- +
+ -
- - Vercel logomark - Start Game - - - +
+
+ + + +
+
+ +
+ + +
+ + + + +
+
-
-
- ); + + ); } diff --git a/frontend/public/add.svg b/frontend/public/add.svg new file mode 100644 index 0000000..3f5f74d --- /dev/null +++ b/frontend/public/add.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/public/next.svg b/frontend/public/next.svg index 5174b28..3391548 100644 --- a/frontend/public/next.svg +++ b/frontend/public/next.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/frontend/public/play-button.svg b/frontend/public/play-button.svg new file mode 100644 index 0000000..77ef9a6 --- /dev/null +++ b/frontend/public/play-button.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/public/plus.svg b/frontend/public/plus.svg new file mode 100644 index 0000000..69ed0cc --- /dev/null +++ b/frontend/public/plus.svg @@ -0,0 +1,7 @@ + + + + + + plus Created with Sketch Beta. + \ No newline at end of file diff --git a/frontend/public/reset.svg b/frontend/public/reset.svg new file mode 100644 index 0000000..74bcfd8 --- /dev/null +++ b/frontend/public/reset.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file