diff --git a/backend/database.db b/backend/database.db index 5586df9..0dee59e 100644 Binary files a/backend/database.db and b/backend/database.db differ diff --git a/backend/server.js b/backend/server.js index 1927423..3b2a259 100644 --- a/backend/server.js +++ b/backend/server.js @@ -68,7 +68,7 @@ app.post('/login', (req, res) => { if (!row || !(await bcrypt.compare(password, row.password))) { return res.status(400).json({ error: 'Invalid username or password.' }); } - res.json({ message: 'Login successful!', userId: row.id }); + res.json({ success: true, message: 'Login successful!', userId: row.id }); }); }); @@ -87,19 +87,39 @@ app.get('/games/:userId', (req, res) => { ); }); -//Join Game + +// Join Game app.post('/joinGame/:gameId', (req, res) => { const gameId = req.params.gameId; const userId = req.body.userId; - const stmt = db.prepare('INSERT INTO games (participants) VALUES (?) WHERE game_id IS (?)'); - stmt.run([gameId, userId], function(err) { + db.get('SELECT * FROM games WHERE game_id = ?', [gameId], (err, game) => { if (err) { - return res.status(400).json({ error: 'Failed to create game.' }); + return res.status(500).json({ error: 'Internal server error' }); } - res.status(201).json({message: "User joined Game!", gameId: this.gameId}) - }) -stmt.finalize(); + if (!game) { + return res.status(404).json({ error: 'Game not found' }); + } + + const participants = JSON.parse(game.participants); + const gameMasterId = game.game_master_id; + + // Check if the user is already a participant or the game master + if (gameMasterId === userId || participants.includes(userId)) { + return res.status(400).json({ error: 'User is already a participant or the game master.' }); + } + + participants.push(userId); + + const stmt = db.prepare('UPDATE games SET participants = ? WHERE game_id = ?'); + stmt.run([JSON.stringify(participants), gameId], function (err) { + if (err) { + return res.status(400).json({ error: 'Failed to join game.' }); + } + res.status(201).json({ message: 'User joined game successfully!', gameId: gameId }); + }); + stmt.finalize(); + }); }); @@ -119,8 +139,7 @@ app.post('/games', (req, res) => { app.post('/createGame', (req, res) => { const { name, description, gameMasterId, participants } = req.body; - // Generate an 8-character alphanumeric ID - const gameId = crypto.randomBytes(4).toString('hex'); // 8 characters (4 bytes * 2 hex chars per byte) + const gameId = crypto.randomBytes(4).toString('hex'); const stmt = db.prepare('INSERT INTO games (game_id, name, description, game_master_id, participants) VALUES (?, ?, ?, ?, ?)'); stmt.run([gameId, name, description, gameMasterId, JSON.stringify(participants)], function (err) { diff --git a/frontend/package.json b/frontend/package.json index fc4529c..9f33ceb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", "react-router-dom": "^7.0.2" }, "devDependencies": { diff --git a/frontend/src/App.css b/frontend/src/App.css index 6ff8af4..1811da8 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -23,6 +23,8 @@ body { margin: 20px; } + + .btn { background-color: #764ACB; color: #f0f0f5; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 944ff02..e12269a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { Helmet } from 'react-helmet'; import './App.css'; import Header from './components/header'; import Home from './pages/home'; @@ -7,6 +8,7 @@ import Login from './pages/login'; import Register from './pages/register'; import JoinGame from './pages/joinGame'; import StartGame from './pages/startGame'; +import { UserProvider } from './context/UserContext.jsx'; function App() { const [isLoggedIn, setIsLoggedIn] = useState(false); @@ -18,41 +20,45 @@ function App() { }; return ( - -
- {popupMessage &&
{popupMessage}
} + + + +
+ + DND Master + + {popupMessage &&
{popupMessage}
} +
- {/* Header is placed at the top of the page */} -
- - - } - /> - } - /> - { - setIsLoggedIn(status); - showPopup('User registered successfully!'); - }} />} - /> - } - /> - } - /> - -
-
+ + } + /> + } + /> + { + setIsLoggedIn(status); + showPopup('User registered successfully!'); + }} />} + /> + } + /> + } + /> + +
+
+ ); } -export default App; +export default App; \ No newline at end of file diff --git a/frontend/src/context/UserContext.jsx b/frontend/src/context/UserContext.jsx new file mode 100644 index 0000000..476aa79 --- /dev/null +++ b/frontend/src/context/UserContext.jsx @@ -0,0 +1,13 @@ +import React, { createContext, useState } from 'react'; + +export const UserContext = createContext(); + +export const UserProvider = ({ children }) => { + const [userId, setUserId] = useState(null); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/frontend/src/pages/games.css b/frontend/src/pages/games.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/pages/games.jsx b/frontend/src/pages/games.jsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/pages/home.css b/frontend/src/pages/home.css new file mode 100644 index 0000000..4507f50 --- /dev/null +++ b/frontend/src/pages/home.css @@ -0,0 +1,65 @@ +.container { + padding: 20px; +} + +.content { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.button-group { + display: flex; + + align-self: center; + flex-direction: row; + margin-bottom: 10px; +} + +.button-group .btn { + margin-bottom: 10px; + padding: 10px 20px; + color: white; + border: none; + cursor: pointer; + text-align: center; +} + +.table-container { + width: 100%; +} + +.games-table { + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; + background-color: #333; + color: #fff; +} + +.games-table th, .games-table td { + border: 1px solid #555; + padding: 8px; +} + +.games-table th { + background-color: #444; + text-align: left; +} + +.games-table tr:nth-child(even) { + background-color: #3a3a3a; +} + +.games-table tr:hover { + background-color: #555; +} + +.game-link { + color: #1e90ff; + text-decoration: none; +} + +.game-link:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/frontend/src/pages/home.jsx b/frontend/src/pages/home.jsx index b4d624b..354f9aa 100644 --- a/frontend/src/pages/home.jsx +++ b/frontend/src/pages/home.jsx @@ -1,8 +1,25 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; +import React, { useContext, useEffect, useState } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import axios from 'axios'; +import { UserContext } from '../context/UserContext'; +import './home.css'; function Home({ isLoggedIn, setIsLoggedIn }) { const navigate = useNavigate(); + const [games, setGames] = useState([]); + const { userId } = useContext(UserContext); + + useEffect(() => { + if (isLoggedIn && userId) { + axios.get(`http://localhost:5000/games/${userId}`) + .then(response => { + setGames(response.data); + }) + .catch(error => { + console.error('Error fetching games:', error); + }); + } + }, [isLoggedIn, userId]); const handleLogout = () => { setIsLoggedIn(false); @@ -10,21 +27,48 @@ function Home({ isLoggedIn, setIsLoggedIn }) { return (
-

Welcome to the Game

+

Welcome to the Site

{!isLoggedIn ? (
) : ( -
- - - +
+
+ + + +
+
+

Your Games

+ + + + + + + + + + {games.map(game => ( + + + + + + ))} + +
Game IDNameDescription
+ + {game.game_id} + + {game.name}{game.description}
+
)}
); } -export default Home; +export default Home; \ No newline at end of file diff --git a/frontend/src/pages/joinGame.jsx b/frontend/src/pages/joinGame.jsx index d0de248..384dacf 100644 --- a/frontend/src/pages/joinGame.jsx +++ b/frontend/src/pages/joinGame.jsx @@ -1,20 +1,16 @@ - -import React, { useState } from 'react'; +import React, { useState, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; +import axios from 'axios'; +import { UserContext } from '../context/UserContext'; const JoinGamePage = () => { const [gameCode, setGameCode] = useState(''); const [error, setError] = useState(''); const navigate = useNavigate(); - - // Mock function to check if game exists (replace with your real logic) - const checkGameExists = (code) => { - const availableGames = ['game123', 'game456', 'game789']; // Example game codes - return availableGames.includes(code); - }; + const { userId } = useContext(UserContext); // Handle form submission - const handleJoinGame = (e) => { + const handleJoinGame = async (e) => { e.preventDefault(); if (gameCode.trim() === '') { @@ -22,14 +18,16 @@ const JoinGamePage = () => { return; } - if (checkGameExists(gameCode)) { - // Simulate adding user to the game (replace with your actual logic) - setError(''); - setTimeout(() => { - navigate('/games'); // Redirect to the games page - }, 1000); - } else { - setError('Game not found!'); + try { + const response = await axios.post(`http://localhost:5000/joinGame/${gameCode}`, { userId }); + if (response.status === 201) { + setError(''); + navigate(`/games/${gameCode}`); // Redirect to the games page + } else { + setError('Failed to join the game.'); + } + } catch (err) { + setError(err.response ? err.response.data.error : 'Error joining the game'); } }; @@ -50,4 +48,4 @@ const JoinGamePage = () => { ); }; -export default JoinGamePage; +export default JoinGamePage; \ No newline at end of file diff --git a/frontend/src/pages/login.jsx b/frontend/src/pages/login.jsx index 4fbd679..80a3706 100644 --- a/frontend/src/pages/login.jsx +++ b/frontend/src/pages/login.jsx @@ -1,22 +1,29 @@ -import React, { useState } from 'react'; +import React, { useState, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; +import { UserContext } from '../context/UserContext'; function Login({ setIsLoggedIn }) { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [message, setMessage] = useState(''); const navigate = useNavigate(); + const { setUserId } = useContext(UserContext); const handleLogin = async (e) => { e.preventDefault(); try { const res = await axios.post('http://localhost:5000/login', { username, password }); setMessage(res.data.message); - setIsLoggedIn(true); - navigate('/'); + if (res.data.success) { + setIsLoggedIn(true); + setUserId(res.data.userId); + navigate('/'); + } else { + setMessage(res.data.message); + } } catch (err) { - setMessage(err.response.data.error); + setMessage(err.response ? err.response.data.error : 'Error logging in'); } }; diff --git a/frontend/src/pages/startGame.jsx b/frontend/src/pages/startGame.jsx index 4028de5..216331c 100644 --- a/frontend/src/pages/startGame.jsx +++ b/frontend/src/pages/startGame.jsx @@ -28,8 +28,8 @@ const startGame = () => { body: JSON.stringify({ name, description, - gameMasterId: 1, // Placeholder for game master ID; replace with dynamic value if needed - participants: [], // Default empty participants array + gameMasterId: 1, + participants: [], }), }); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5e8063a..d154be1 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1858,7 +1858,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prop-types@^15.8.1: +prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -1885,6 +1885,21 @@ react-dom@^18.3.1: loose-envify "^1.1.0" scheduler "^0.23.2" +react-fast-compare@^3.1.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + +react-helmet@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" + integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== + dependencies: + object-assign "^4.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.1.1" + react-side-effect "^2.1.0" + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -1912,6 +1927,11 @@ react-router@7.0.2: set-cookie-parser "^2.6.0" turbo-stream "2.4.0" +react-side-effect@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" + integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw== + react@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"