+
This commit is contained in:
Binary file not shown.
+68
-7
@@ -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
|
// Join Game
|
||||||
app.post('/joinGame/:gameId', (req, res) => {
|
app.post('/joinGame/:gameId', (req, res) => {
|
||||||
const gameId = req.params.gameId;
|
const gameId = req.params.gameId;
|
||||||
@@ -162,18 +177,25 @@ app.post('/createGame', (req, res) => {
|
|||||||
// Fetch game Participant Characters
|
// Fetch game Participant Characters
|
||||||
app.get('/games/:gameId/playerchars', (req, res) => {
|
app.get('/games/:gameId/playerchars', (req, res) => {
|
||||||
const gameId = req.params.gameId;
|
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) {
|
if (err) {
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
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
|
// Fetch game NPCs
|
||||||
app.get('/games/:gameId/npcs', (req, res) => {
|
app.get('/games/:gameId/npcs', (req, res) => {
|
||||||
const gameId = req.params.gameId;
|
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) {
|
if (err) {
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
@@ -184,12 +206,19 @@ app.get('/games/:gameId/npcs', (req, res) => {
|
|||||||
// Fetch game Items
|
// Fetch game Items
|
||||||
app.get('/games/:gameId/items', (req, res) => {
|
app.get('/games/:gameId/items', (req, res) => {
|
||||||
const gameId = req.params.gameId;
|
const gameId = req.params.gameId;
|
||||||
db.get('SELECT * FROM Item WHERE GameID = ?', [gameId], (err, row) => {
|
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) {
|
if (err) {
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
res.json(row);
|
res.json(rows);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -324,6 +353,38 @@ app.get('/games/:gameId/:charId/items', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Item Part
|
// 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
|
// Set Item Owner
|
||||||
app.post('/games/:charId/:itemId/owner', (req, res) => {
|
app.post('/games/:charId/:itemId/owner', (req, res) => {
|
||||||
const gameId = req.params.gameId;
|
const gameId = req.params.gameId;
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import JoinGame from './pages/joinGame';
|
|||||||
import StartGame from './pages/startGame';
|
import StartGame from './pages/startGame';
|
||||||
import Profile from './pages/profile';
|
import Profile from './pages/profile';
|
||||||
import Games from './pages/games.jsx';
|
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 CreateCharacter from './pages/createCharacter.jsx';
|
||||||
import { UserProvider } from './context/UserContext.jsx';
|
import { UserProvider } from './context/UserContext.jsx';
|
||||||
|
|
||||||
@@ -61,6 +63,10 @@ function App() {
|
|||||||
path="/profile"
|
path="/profile"
|
||||||
element={<Profile isLoggedIn={isLoggedIn} />}
|
element={<Profile isLoggedIn={isLoggedIn} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='/games/:gameId/master'
|
||||||
|
element={<GameMasterPage isLoggedIn={isLoggedIn} />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='/games/:gameId'
|
path='/games/:gameId'
|
||||||
element={<Games isLoggedIn={isLoggedIn} />}
|
element={<Games isLoggedIn={isLoggedIn} />}
|
||||||
@@ -69,6 +75,11 @@ function App() {
|
|||||||
path='/create-Character'
|
path='/create-Character'
|
||||||
element={<CreateCharacter isLoggedIn={isLoggedIn} />}
|
element={<CreateCharacter isLoggedIn={isLoggedIn} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='/create-item'
|
||||||
|
element={<CreateItem isLoggedIn={isLoggedIn} />}
|
||||||
|
/>
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Grid2 container spacing={3}>
|
||||||
|
{/* Left Column - Image and Basic Info */}
|
||||||
|
<Grid2 item xs={12} md={4}>
|
||||||
|
<Box sx={{ border: '1px solid #444', borderRadius: '3px', p: 2, backgroundColor: '#2e2e3f', width: '330px' }}>
|
||||||
|
<Card sx={{ backgroundColor: '#1e1e2f', color: '#fff' }}>
|
||||||
|
{/* Image Upload Section */}
|
||||||
|
{imagePreview ? (
|
||||||
|
<Box component="label" htmlFor="image-upload" sx={{ cursor: 'pointer', position: 'relative' }}>
|
||||||
|
<input
|
||||||
|
accept="image/*"
|
||||||
|
type="file"
|
||||||
|
id="image-upload"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleImageChange}
|
||||||
|
/>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
height="300"
|
||||||
|
image={imagePreview}
|
||||||
|
alt="Item Preview"
|
||||||
|
sx={{ borderRadius: '3px', objectFit: 'contain', margin: '0 auto', borderBottom: '1px solid #444' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
component="label"
|
||||||
|
htmlFor="image-upload"
|
||||||
|
sx={{
|
||||||
|
height: 300,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#2e2e3f',
|
||||||
|
borderRadius: '3px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
accept="image/*"
|
||||||
|
type="file"
|
||||||
|
id="image-upload"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleImageChange}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
component="span"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': { backgroundColor: '#9865f7' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Upload Item Image
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Basic Item Info */}
|
||||||
|
<CardContent sx={{ p: 3 }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Item Name"
|
||||||
|
name="ItemName" // Changed from itemName
|
||||||
|
value={formData.ItemName}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles, mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Grid2 container spacing={2}>
|
||||||
|
<Grid2 item xs={6}>
|
||||||
|
<FormControl fullWidth sx={{ mb: 2, width: '140px' }}> {/* Width for "Consumable" + padding */}
|
||||||
|
<InputLabel sx={{ color: '#fff' }}>Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="Type"
|
||||||
|
value={formData.Type}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles }}
|
||||||
|
>
|
||||||
|
<MenuItem value="Weapon">Weapon</MenuItem>
|
||||||
|
<MenuItem value="Armor">Armor</MenuItem>
|
||||||
|
<MenuItem value="Consumable">Consumable</MenuItem>
|
||||||
|
<MenuItem value="Quest">Quest Item</MenuItem>
|
||||||
|
<MenuItem value="Other">Other</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item xs={6}>
|
||||||
|
<FormControl fullWidth sx={{ mb: 2, width: '120px' }}> {/* Width for "Potion" + padding */}
|
||||||
|
<InputLabel sx={{ color: '#fff' }}>Art</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="Art"
|
||||||
|
value={formData.Art}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles }}
|
||||||
|
>
|
||||||
|
<MenuItem value="Sword">Sword</MenuItem>
|
||||||
|
<MenuItem value="Axe">Axe</MenuItem>
|
||||||
|
<MenuItem value="Bow">Bow</MenuItem>
|
||||||
|
<MenuItem value="Shield">Shield</MenuItem>
|
||||||
|
<MenuItem value="Staff">Staff</MenuItem>
|
||||||
|
<MenuItem value="Potion">Potion</MenuItem>
|
||||||
|
<MenuItem value="Other">Other</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Gold Value"
|
||||||
|
name="GoldValue"
|
||||||
|
type="number"
|
||||||
|
value={formData.GoldValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles, mb: 2 }}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
{/* Right Column - Details */}
|
||||||
|
<Grid2 item xs={12} md={8}>
|
||||||
|
{/* Item Properties */}
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Item Properties</Typography>
|
||||||
|
<Box sx={{ backgroundColor: '#2e2e3f', p: 2, borderRadius: '8px', border: '1px solid #444', width: '800px', justifyItems: 'center' }}>
|
||||||
|
<Grid2 container spacing={2}>
|
||||||
|
<Grid2 item xs={12} md={4}>
|
||||||
|
<FormControl fullWidth sx={{ mb: 2, width: '160px' }}> {/* Width for "Legendary" + padding */}
|
||||||
|
<InputLabel sx={{ color: '#fff' }}>Rarity</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="Rarity"
|
||||||
|
value={formData.Rarity}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles }}
|
||||||
|
>
|
||||||
|
<MenuItem value={1}>Poor</MenuItem>
|
||||||
|
<MenuItem value={2}>Common</MenuItem>
|
||||||
|
<MenuItem value={3}>Uncommon</MenuItem>
|
||||||
|
<MenuItem value={4}>Rare</MenuItem>
|
||||||
|
<MenuItem value={5}>Epic</MenuItem>
|
||||||
|
<MenuItem value={6}>Legendary</MenuItem>
|
||||||
|
<MenuItem value={7}>Artifact</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item xs={12} md={4}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Attack Power"
|
||||||
|
name="AP"
|
||||||
|
type="number"
|
||||||
|
value={formData.AP}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={{ ...inputStyles }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item xs={12} md={4}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Max Durability"
|
||||||
|
name="MaxDurability"
|
||||||
|
type="number"
|
||||||
|
value={formData.MaxDurability}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Abilities */}
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Abilities</Typography>
|
||||||
|
<Box sx={{ backgroundColor: '#2e2e3f', p: 2, borderRadius: '8px', border: '1px solid #444' }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={2}
|
||||||
|
name="Abilities"
|
||||||
|
value={formData.Abilities}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={{ ...inputStyles }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': { backgroundColor: '#9865f7' },
|
||||||
|
mt: 3
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create Item
|
||||||
|
</Button>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateItem;
|
||||||
@@ -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 }) => (
|
||||||
|
<Box sx={{
|
||||||
|
mb: 4,
|
||||||
|
backgroundColor: '#2e2e3f',
|
||||||
|
p: 2,
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #444'
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
|
<Typography variant="h5" sx={{ color: '#fff' }}>{title}</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
component={Link}
|
||||||
|
to={`${createPath}?gameId=${gameId}`}
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': { backgroundColor: '#9865f7' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{createText}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Grid2 container spacing={2}>
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||||
|
<Card sx={{
|
||||||
|
backgroundColor: '#1e1e2f',
|
||||||
|
color: '#fff',
|
||||||
|
height: '95%', // Reduced height
|
||||||
|
border: '1px solid #444',
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '8px'
|
||||||
|
}}>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
height="135" // Reduced from 140
|
||||||
|
image={item.Img || defaultItemImage}
|
||||||
|
alt={item.ItemName}
|
||||||
|
className={`rarity-${item.Rarity} rarity-image`}
|
||||||
|
sx={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
width: '128px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<CardContent>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
component="div"
|
||||||
|
className={`rarity-name-${item.Rarity}`}
|
||||||
|
>
|
||||||
|
{item.ItemName}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: '#bbb' }}>
|
||||||
|
Type: {item.Type}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: '#bbb' }}>
|
||||||
|
Value: {item.GoldValue}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: '#bbb' }}>
|
||||||
|
Owner: {item.OwnerName || 'Unassigned'}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
))}
|
||||||
|
</Grid2>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// Update main container Box styling
|
||||||
|
<Box sx={{
|
||||||
|
p: 3,
|
||||||
|
background: 'rgba(30, 30, 47, 0.9)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
width: '1200px',
|
||||||
|
margin: '80px auto 40px auto', // Increased top margin
|
||||||
|
position: 'relative',
|
||||||
|
minHeight: 'calc(100vh - 120px)' // Account for margins
|
||||||
|
}}>
|
||||||
|
<Typography variant="h4" sx={{ mb: 4, color: '#fff' }}>
|
||||||
|
Game Master Dashboard
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Player Characters Section */}
|
||||||
|
<Box sx={{
|
||||||
|
mb: 4,
|
||||||
|
backgroundColor: '#2e2e3f',
|
||||||
|
p: 2,
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #444'
|
||||||
|
}}>
|
||||||
|
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Player Characters</Typography>
|
||||||
|
<Grid2 container spacing={2}>
|
||||||
|
{playerCharacters.map((character, index) => (
|
||||||
|
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||||
|
<Card sx={{
|
||||||
|
backgroundColor: '#1e1e2f',
|
||||||
|
color: '#fff',
|
||||||
|
border: '1px solid #444',
|
||||||
|
height: '100%'
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '8px'
|
||||||
|
}}>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
height="140"
|
||||||
|
image={character.Img || defaultCharacterImage}
|
||||||
|
alt={character.CharName}
|
||||||
|
sx={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
width: '140px',
|
||||||
|
borderRadius: '3px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h6" component="div">
|
||||||
|
{character.CharName}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: '#bbb' }}>
|
||||||
|
{character.Race} - Level {character.Level}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: '#bbb' }}>
|
||||||
|
{character.Job}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
))}
|
||||||
|
</Grid2>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* NPCs Section */}
|
||||||
|
<Box sx={{
|
||||||
|
mb: 4,
|
||||||
|
backgroundColor: '#2e2e3f',
|
||||||
|
p: 2,
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #444'
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
|
<Typography variant="h5" sx={{ color: '#fff' }}>NPCs</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
component={Link}
|
||||||
|
to={`/create-npc?gameId=${gameId}`}
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': { backgroundColor: '#9865f7' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create NPC
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Grid2 container spacing={2}>
|
||||||
|
{npcs.map((npc, index) => (
|
||||||
|
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||||
|
<Card sx={{
|
||||||
|
backgroundColor: '#1e1e2f',
|
||||||
|
color: '#fff',
|
||||||
|
border: '1px solid #444',
|
||||||
|
height: '100%'
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '8px'
|
||||||
|
}}>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
height="140"
|
||||||
|
image={npc.Img || defaultCharacterImage}
|
||||||
|
alt={npc.CharName}
|
||||||
|
sx={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
width: '140px',
|
||||||
|
borderRadius: '3px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h6" component="div">
|
||||||
|
{npc.CharName}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: '#bbb' }}>
|
||||||
|
{npc.Race}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: '#bbb' }}>
|
||||||
|
{npc.Job}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
))}
|
||||||
|
</Grid2>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Items Section */}
|
||||||
|
<Section
|
||||||
|
title="Items"
|
||||||
|
items={items}
|
||||||
|
createPath="/create-item"
|
||||||
|
createText="Create Item"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GameMasterPage;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useContext } from 'react';
|
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 axios from 'axios';
|
||||||
import { UserContext } from '../context/UserContext';
|
import { UserContext } from '../context/UserContext';
|
||||||
import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from '@mui/material';
|
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 GamesPage = () => {
|
||||||
const { userId } = useContext(UserContext);
|
const { userId } = useContext(UserContext);
|
||||||
const { gameId } = useParams();
|
const { gameId } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [character, setCharacter] = useState(null);
|
const [character, setCharacter] = useState(null);
|
||||||
const [inventory, setInventory] = useState([]);
|
const [inventory, setInventory] = useState([]);
|
||||||
const [isEditOpen, setIsEditOpen] = useState(false);
|
const [isEditOpen, setIsEditOpen] = useState(false);
|
||||||
@@ -68,6 +69,29 @@ const GamesPage = () => {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [character]);
|
}, [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 = () => {
|
const handleEditOpen = () => {
|
||||||
setNewDescription(character.description);
|
setNewDescription(character.description);
|
||||||
setIsEditOpen(true);
|
setIsEditOpen(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user