- Can now join games
- Can continue games using table
- Can create accounts
- Can create Games
This commit is contained in:
Marces Zastrow
2025-01-08 08:25:00 +01:00
parent 5a46333bfd
commit 1cbe8b9d94
14 changed files with 252 additions and 77 deletions
Binary file not shown.
+27 -8
View File
@@ -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,20 +87,40 @@ app.get('/games/:userId', (req, res) => {
);
});
// 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})
})
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();
});
});
// Create a new game
@@ -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) {
+1
View File
@@ -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": {
+2
View File
@@ -23,6 +23,8 @@ body {
margin: 20px;
}
.btn {
background-color: #764ACB;
color: #f0f0f5;
+8 -2
View File
@@ -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,11 +20,14 @@ function App() {
};
return (
<UserProvider>
<meta name="DND Master" content="WoW. A description."/>
<Router>
<div>
<Helmet>
<title>DND Master</title>
</Helmet>
{popupMessage && <div className="popup">{popupMessage}</div>}
{/* Header is placed at the top of the page */}
<Header isLoggedIn={isLoggedIn} />
<Routes>
@@ -52,6 +57,7 @@ function App() {
</Routes>
</div>
</Router>
</UserProvider>
);
}
+13
View File
@@ -0,0 +1,13 @@
import React, { createContext, useState } from 'react';
export const UserContext = createContext();
export const UserProvider = ({ children }) => {
const [userId, setUserId] = useState(null);
return (
<UserContext.Provider value={{ userId, setUserId }}>
{children}
</UserContext.Provider>
);
};
View File
View File
+65
View File
@@ -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;
}
+47 -3
View File
@@ -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,18 +27,45 @@ function Home({ isLoggedIn, setIsLoggedIn }) {
return (
<div className="container">
<h2>Welcome to the Game</h2>
<h2>Welcome to the Site</h2>
{!isLoggedIn ? (
<div className="button-group">
<button className="btn" onClick={() => navigate('/login')}>Login</button>
<button className="btn" onClick={() => navigate('/register')}>Register</button>
</div>
) : (
<div className="content">
<div className="button-group">
<button className="btn" onClick={() => navigate('/startGame')}>Start Game</button>
<button className="btn" onClick={() => navigate('/joinGame')}>Join Game</button>
<button className="btn" onClick={handleLogout}>Logout</button>
</div>
<div className="table-container">
<h3>Your Games</h3>
<table className="games-table">
<thead>
<tr>
<th>Game ID</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{games.map(game => (
<tr key={game.game_id}>
<td>
<Link to={`/games/${game.game_id}`} className="game-link">
{game.game_id}
</Link>
</td>
<td>{game.name}</td>
<td>{game.description}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
);
+13 -15
View File
@@ -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)
try {
const response = await axios.post(`http://localhost:5000/joinGame/${gameCode}`, { userId });
if (response.status === 201) {
setError('');
setTimeout(() => {
navigate('/games'); // Redirect to the games page
}, 1000);
navigate(`/games/${gameCode}`); // Redirect to the games page
} else {
setError('Game not found!');
setError('Failed to join the game.');
}
} catch (err) {
setError(err.response ? err.response.data.error : 'Error joining the game');
}
};
+9 -2
View File
@@ -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);
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');
}
};
+2 -2
View File
@@ -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: [],
}),
});
+21 -1
View File
@@ -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"