diff --git a/backend/database.db b/backend/database.db index 662d7f6..71c46ba 100644 Binary files a/backend/database.db and b/backend/database.db differ diff --git a/backend/server.js b/backend/server.js index 3ee6d7b..a02b803 100644 --- a/backend/server.js +++ b/backend/server.js @@ -95,6 +95,21 @@ app.get('/games/:userId', (req, res) => { }); +// Fetch game details including game master +app.get('/games/:gameId/master', (req, res) => { + const gameId = req.params.gameId; + db.get('SELECT * FROM games WHERE game_id = ?', [gameId], (err, row) => { + if (err) { + return res.status(500).json({ error: 'Internal server error' }); + } + if (!row) { + return res.status(404).json({ error: 'Game not found' }); + } + // Convert game_master_id to number for consistent comparison + res.json(row); + }); + }); + // Join Game app.post('/joinGame/:gameId', (req, res) => { const gameId = req.params.gameId; @@ -162,18 +177,25 @@ app.post('/createGame', (req, res) => { // Fetch game Participant Characters app.get('/games/:gameId/playerchars', (req, res) => { const gameId = req.params.gameId; - db.get('SELECT * FROM PlayerCharacter WHERE GameID = ?', [gameId], (err, row) => { + db.all('SELECT * FROM PlayerCharacter WHERE GameID = ?', [gameId], (err, rows) => { if (err) { return res.status(500).json({ error: 'Internal server error' }); } - res.json(row); + // Process each row and convert BLOB to base64 if image exists + const processedRows = rows.map(row => { + if (row.Img) { + row.Img = `data:image/jpeg;base64,${row.Img.toString('base64')}`; + } + return row; + }); + res.json(processedRows); }); -}) +}); // Fetch game NPCs app.get('/games/:gameId/npcs', (req, res) => { const gameId = req.params.gameId; - db.get('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, row) => { + db.all('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, row) => { if (err) { return res.status(500).json({ error: 'Internal server error' }); } @@ -184,12 +206,19 @@ app.get('/games/:gameId/npcs', (req, res) => { // Fetch game Items app.get('/games/:gameId/items', (req, res) => { const gameId = req.params.gameId; - db.get('SELECT * FROM Item WHERE GameID = ?', [gameId], (err, row) => { - if (err) { - return res.status(500).json({ error: 'Internal server error' }); + db.all(` + SELECT Item.*, PlayerCharacter.CharName as OwnerName + FROM Item + LEFT JOIN PlayerCharacter ON Item.OwnerID = PlayerCharacter.CharID + WHERE Item.GameID = ?`, + [gameId], + (err, rows) => { + if (err) { + return res.status(500).json({ error: 'Internal server error' }); + } + res.json(rows); } - res.json(row); - }); + ); }) @@ -324,6 +353,38 @@ app.get('/games/:gameId/:charId/items', (req, res) => { }); // Item Part +// Create Item +app.post('/games/item/create', upload.single('image'), (req, res) => { + const { + ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability, + GoldValue, Abilities, GameID, AP + } = req.body; + + const imageBuffer = req.file ? req.file.buffer : null; + + const stmt = db.prepare(` + INSERT INTO Item ( + ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability, + GoldValue, Abilities, GameID, OwnerID, img, AP + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?) + `); + + stmt.run([ + ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability, + GoldValue, Abilities, GameID, imageBuffer, AP + ], function(err) { + if (err) { + console.error('Database error:', err); + return res.status(500).json({ error: 'Internal server error' }); + } + res.status(201).json({ + message: 'Item created successfully!', + itemId: this.lastID + }); + }); + stmt.finalize(); +}); + // Set Item Owner app.post('/games/:charId/:itemId/owner', (req, res) => { const gameId = req.params.gameId; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 96bd4d5..08b941e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -10,6 +10,8 @@ import JoinGame from './pages/joinGame'; import StartGame from './pages/startGame'; import Profile from './pages/profile'; import Games from './pages/games.jsx'; +import GameMasterPage from './pages/gameMasterPage.jsx'; +import CreateItem from './pages/createItem.jsx'; import CreateCharacter from './pages/createCharacter.jsx'; import { UserProvider } from './context/UserContext.jsx'; @@ -61,6 +63,10 @@ function App() { path="/profile" element={} /> + } + /> } @@ -69,6 +75,11 @@ function App() { path='/create-Character' element={} /> + } + /> + diff --git a/frontend/src/pages/createItem.jsx b/frontend/src/pages/createItem.jsx new file mode 100644 index 0000000..a132c1d --- /dev/null +++ b/frontend/src/pages/createItem.jsx @@ -0,0 +1,308 @@ +import React, { useState, useContext } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { UserContext } from '../context/UserContext'; +import axios from 'axios'; +import { Box, TextField, Button, Typography, Select, MenuItem, FormControl, InputLabel, Grid2, Card, CardContent, CardMedia } from '@mui/material'; + +import defaultItemImage from '../assets/default-item.png'; + +const CreateItem = () => { + const navigate = useNavigate(); + const location = useLocation(); + const { userId } = useContext(UserContext); + const gameId = new URLSearchParams(location.search).get('gameId'); + + const [formData, setFormData] = useState({ + ItemName: '', // Match DB column names exactly + Type: '', + Art: '', + Rarity: 1, + MaxDurability: 100, + CurrentDurability: 100, + GoldValue: 0, + Abilities: '', + AP: 0, + GameID: gameId + }); + + const [selectedImage, setSelectedImage] = useState(null); + const [imagePreview, setImagePreview] = useState(null); + + const handleChange = (e) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }); + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file) { + setSelectedImage(file); + setImagePreview(URL.createObjectURL(file)); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + const formDataToSend = new FormData(); + + // Add all form fields with exact column names + Object.keys(formData).forEach(key => { + // Convert null values to empty strings to prevent SQL issues + formDataToSend.append(key, formData[key] === null ? '' : formData[key]); + }); + + // Add image if present + if (selectedImage) { + formDataToSend.append('image', selectedImage); + } + + try { + const response = await axios.post( + 'http://localhost:5000/games/item/create', + formDataToSend, + { + headers: { + 'Content-Type': 'multipart/form-data' + } + } + ); + + if (response.status === 201) { + navigate(`/games/${gameId}/master`); + } + } catch (error) { + console.error('Error creating item:', error); + } +}; + + const inputStyles = { + '& .MuiOutlinedInput-root': { + '& fieldset': { borderColor: '#444' }, + '&:hover fieldset': { borderColor: '#764ACB' }, + '&.Mui-focused fieldset': { borderColor: '#764ACB' }, + }, + '& .MuiInputLabel-root': { color: '#fff' }, + '& .MuiInputBase-input': { color: '#fff' } + }; + + return ( + +
+ + {/* Left Column - Image and Basic Info */} + + + + {/* Image Upload Section */} + {imagePreview ? ( + + + + + ) : ( + + + + + )} + + {/* Basic Item Info */} + + + + + + {/* Width for "Consumable" + padding */} + Type + + + + + {/* Width for "Potion" + padding */} + Art + + + + + + + + + + + + {/* Right Column - Details */} + + {/* Item Properties */} + + Item Properties + + + + {/* Width for "Legendary" + padding */} + Rarity + + + + + + + + + + + + + + {/* Abilities */} + + Abilities + + + + + + {/* Submit Button */} + + + +
+
+ ); +}; + +export default CreateItem; \ No newline at end of file diff --git a/frontend/src/pages/createNpc.jsx b/frontend/src/pages/createNpc.jsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/pages/gameMasterPage.jsx b/frontend/src/pages/gameMasterPage.jsx new file mode 100644 index 0000000..68d570a --- /dev/null +++ b/frontend/src/pages/gameMasterPage.jsx @@ -0,0 +1,267 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { Link, useParams } from 'react-router-dom'; +import { UserContext } from '../context/UserContext'; +import axios from 'axios'; +import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; + +import defaultCharacterImage from '../assets/default-character.png'; +import defaultItemImage from '../assets/default-item.png'; + +const GameMasterPage = () => { + const { userId } = useContext(UserContext); + const { gameId } = useParams(); + const [playerCharacters, setPlayerCharacters] = useState([]); + const [npcs, setNpcs] = useState([]); + const [items, setItems] = useState([]); + + // Fix useEffect data fetching + useEffect(() => { + const fetchData = async () => { + try { + // Fetch player characters + const pcsResponse = await axios.get(`http://localhost:5000/games/${gameId}/playerchars`); + const processedPCs = Array.isArray(pcsResponse.data) ? pcsResponse.data : [pcsResponse.data]; + setPlayerCharacters(processedPCs.filter(pc => pc !== null)); + + // Fetch NPCs with different structure + const npcsResponse = await axios.get(`http://localhost:5000/games/${gameId}/npcs`); + const processedNPCs = Array.isArray(npcsResponse.data) ? npcsResponse.data : [npcsResponse.data]; + setNpcs(processedNPCs.filter(npc => npc !== null)); + + // Fetch Items + const itemsResponse = await axios.get(`http://localhost:5000/games/${gameId}/items`); + const processedItems = Array.isArray(itemsResponse.data) ? itemsResponse.data : [itemsResponse.data]; + setItems(processedItems.filter(item => item !== null)); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + + fetchData(); + const interval = setInterval(fetchData, 5000); + return () => clearInterval(interval); + }, [gameId]); + + const Section = ({ title, items, createPath, createText }) => ( + + + {title} + + + + {items.map((item, index) => ( + + + + + + + + {item.ItemName} + + + Type: {item.Type} + + + Value: {item.GoldValue} + + + Owner: {item.OwnerName || 'Unassigned'} + + + + + ))} + + + ); + + return ( + // Update main container Box styling + + + Game Master Dashboard + + + {/* Player Characters Section */} + + Player Characters + + {playerCharacters.map((character, index) => ( + + + + + + + + {character.CharName} + + + {character.Race} - Level {character.Level} + + + {character.Job} + + + + + ))} + + + + {/* NPCs Section */} + + + NPCs + + + + {npcs.map((npc, index) => ( + + + + + + + + {npc.CharName} + + + {npc.Race} + + + {npc.Job} + + + + + ))} + + + + {/* Items Section */} +
+ + ); +}; + +export default GameMasterPage; \ No newline at end of file diff --git a/frontend/src/pages/games.jsx b/frontend/src/pages/games.jsx index 4fffb95..9889544 100644 --- a/frontend/src/pages/games.jsx +++ b/frontend/src/pages/games.jsx @@ -1,5 +1,5 @@ import { useState, useEffect, useContext } from 'react'; -import { Link, useParams } from 'react-router-dom'; +import { Link, useParams, useNavigate } from 'react-router-dom'; import axios from 'axios'; import { UserContext } from '../context/UserContext'; import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from '@mui/material'; @@ -21,6 +21,7 @@ import './games.css'; const GamesPage = () => { const { userId } = useContext(UserContext); const { gameId } = useParams(); + const navigate = useNavigate(); const [character, setCharacter] = useState(null); const [inventory, setInventory] = useState([]); const [isEditOpen, setIsEditOpen] = useState(false); @@ -68,6 +69,29 @@ const GamesPage = () => { return () => clearInterval(interval); }, [character]); + useEffect(() => { + const checkGameMaster = async () => { + if (!userId || !gameId) return; + + try { + console.log('Checking if user is game master...'); + const response = await axios.get(`http://localhost:5000/games/${gameId}/master`); + console.log('Game data:', response.data); + console.log('User ID:', userId); + console.log('Game master ID:', response.data.game_master_id); + + if (parseInt(userId) === parseInt(response.data.game_master_id)) { + console.log('User is game master, redirecting...'); + navigate(`/games/${gameId}/master`, { replace: true }); + } + } catch (error) { + console.error('Error checking game master:', error); + } + }; + + checkGameMaster(); + }, [userId, gameId, navigate]); + const handleEditOpen = () => { setNewDescription(character.description); setIsEditOpen(true);