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
+
+
+
+ | Game ID |
+ Name |
+ Description |
+
+
+
+ {games.map(game => (
+
+ |
+
+ {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"