diff --git a/client/html/components/navbar.html b/client/html/components/navbar.html index 95d5dba..54a1bf4 100644 --- a/client/html/components/navbar.html +++ b/client/html/components/navbar.html @@ -129,7 +129,7 @@ Profile
  • - Avatar + Avatar
  • Friends @@ -165,7 +165,7 @@ >
  • - Avatar + Avatar
  • Friends diff --git a/client/html/pages/my/avatar.html b/client/html/pages/my/avatar.html new file mode 100644 index 0000000..78b04c8 --- /dev/null +++ b/client/html/pages/my/avatar.html @@ -0,0 +1,208 @@ + + + + + + Avatar - Valkyrie + + + + + + + + + + + + + + + + +
    Loading...
    + + +
    +
    +

    Avatar Editor

    +
    +
    +
    +
    +

    Your Avatar

    +
    +
    +
    + Avatar Base + Avatar Shirt + Avatar Pants + Avatar Hat +
    +
    +
    +
    +
    +

    Colors

    +
    +
    +

    Click a body part to change its color:

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +

    Customize Your Avatar

    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Currently Wearing

    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/client/js/avatar.js b/client/js/avatar.js new file mode 100644 index 0000000..3ec77b0 --- /dev/null +++ b/client/js/avatar.js @@ -0,0 +1,250 @@ +// client/js/avatar.js +$(document).ready(function () { + initializeAvatarEditor(); + loadUserAvatar(); +}); + +function initializeAvatarEditor() { + loadAvatarsItems(); + setupItemSelection(); +} + +function loadAvatarsItems() { + loadShirts(); + // Uncomment these when i do these + // loadPants(); + // loadHats(); +} + +function loadShirts() { + const token = localStorage.getItem('token'); + console.log('Loading shirts for user'); + + $.ajax({ + url: '/api/shirts/user', // Correct server route + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + success: function (shirts) { + console.log('Shirts loaded successfully:', shirts.length); + displayUserShirts(shirts); + }, + error: function (xhr, status, error) { + console.error('Error fetching shirts:', error); + console.error('Status:', status); + console.error('Response:', xhr.responseText); + $('#shirts-container').html('
    Error loading shirts. Please try again later.
    '); + }, + }); +} + +function displayUserShirts(shirts) { + const container = $('#shirts-container'); + container.empty(); + + if (!Array.isArray(shirts) || shirts.length === 0) { + console.log('No shirts available to display'); + container.append('
    No shirts available.
    '); + return; + } + + console.log('Displaying shirts:', shirts.length); + shirts.forEach((shirt) => { + if (!shirt || !shirt.Name || !shirt.ThumbnailLocation) { + console.error('Invalid shirt object:', shirt); + return; + } + const shirtHtml = generateItemHtml( + shirt.Name, + shirt.ThumbnailLocation, + shirt.creator ? shirt.creator.username : 'Unknown', + shirt.Price, + shirt._id, + 'shirt' + ); + container.append(shirtHtml); + }); +} + +function generateItemHtml(name, imageSrc, creator, price, id, type) { + const priceDisplay = price === 0 ? 'Free' : `$${price}`; + return ` +
    +
    +
    + ${name} +
    +
    +

    ${name}

    +

    Creator: ${creator}

    +

    Price: ${priceDisplay}

    + +
    +
    +
    + `; +} + +function setupItemSelection() { + $(document).on('click', '.select-item', function () { + const type = $(this).data('type'); + const itemId = $(this).data('id'); + wearItem(type, itemId); + saveAvatarSelection(type, itemId); + }); +} + +function wearItem(type, itemId) { + const token = localStorage.getItem('token'); + let apiUrl = ''; + + switch (type) { + case 'shirt': + apiUrl = `/api/shirts/${itemId}`; + break; + // Add cases for 'pants' and 'hat' when made + default: + return; + } + + $.ajax({ + url: apiUrl, + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + success: function (item) { + updateAvatarDisplay(type, item.thumbnailUrl); // item.ThumbnailLocation + updateCurrentlyWearing(type, item); + }, + error: function (xhr, status, error) { + console.error(`Error fetching ${type}:`, error); + }, + }); +} + +function updateAvatarDisplay(type, imageUrl) { + switch (type) { + case 'shirt': + $('#avatar-shirt').attr('src', imageUrl); + break; + // Add cases for 'pants' and 'hat' when i makethem + default: + break; + } +} + + +function updateCurrentlyWearing(type, item) { + const container = $('#currently-wearing'); + const existingItem = container.find(`[data-type="${type}"]`); + + if (existingItem.length) { + existingItem.remove(); + } + + const itemHtml = ` +
    +
    + ${item.Name} +
    +
    ${item.Name}
    + +
    +
    +
    + `; + + container.append(itemHtml); +} + +function loadUserAvatar() { + const token = localStorage.getItem('token'); + $.ajax({ + url: '/api/avatar', + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + success: function (avatar) { + if (avatar.shirtId) { + wearItem('shirt', avatar.shirtId); + } + }, + error: function (xhr, status, error) { + console.error('Error loading avatar:', error); + $('#avatar-display').html('
    Error loading avatar. Please try again later.
    '); + }, + }); +} + +function saveAvatarSelection(type, itemId) { + const token = localStorage.getItem('token'); + const avatarData = {}; + + switch (type) { + case 'shirt': + avatarData.shirtId = itemId; + break; + default: + return; + } + + $.ajax({ + url: '/api/avatar', + method: 'PUT', + data: JSON.stringify(avatarData), + contentType: 'application/json', + headers: { + Authorization: `Bearer ${token}`, + }, + success: function (response) { + console.log('Avatar updated successfully.'); + }, + error: function (xhr, status, error) { + console.error('Error updating avatar:', error); + }, + }); +} + +// basic body colors set up for seven +function setupBodyColors() { + const bodyParts = ['head', 'torso', 'leftArm', 'rightArm', 'leftLeg', 'rightLeg']; + const colorPicker = $(''); + const bodyColorContainer = $('#body-colors'); + + bodyParts.forEach(part => { + const partButton = $(``); + bodyColorContainer.append(partButton); + }); + + bodyColorContainer.append(colorPicker); + + $('.body-part').on('click', function() { + const part = $(this).data('part'); + colorPicker.data('currentPart', part); + colorPicker.click(); + }); + + colorPicker.on('change', function() { + const color = $(this).val(); + const part = $(this).data('currentPart'); + updateBodyColor(part, color); + }); +} + +function updateBodyColor(part, color) { + // to do +} + +$(document).on('click', '.remove-item', function() { + const type = $(this).data('type'); + removeItem(type); +}); + +function removeItem(type) { + $(`#avatar-${type}`).attr('src', ''); + $(`#currently-wearing [data-type="${type}"]`).remove(); + saveAvatarSelection(type, null); +} \ No newline at end of file diff --git a/server/functions/api/middleware/authenticateToken.js b/server/functions/api/middleware/authenticateToken.js index a2f93e6..2b3cb62 100644 --- a/server/functions/api/middleware/authenticateToken.js +++ b/server/functions/api/middleware/authenticateToken.js @@ -1,21 +1,24 @@ const jwt = require('jsonwebtoken'); function authenticateToken(req, res, next) { - const authHeader = req.headers['authorization']; - const token = authHeader && authHeader.split(' ')[1]; + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; - if (!token) { - return res.status(401).json({ error: 'No token provided.' }); - } - - jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { - if (err) { - console.error('Token verification error:', err); - return res.status(403).json({ error: 'Invalid token.' }); + if (token == null) { + console.error('No token provided'); + return res.sendStatus(401); } - req.user = { userId: decoded.userId }; // Ensure userId is correctly set - next(); - }); + + jwt.verify(token, process.env.JWT_SECRET, (err, user) => { + if (err) { + console.error('Token verification failed:', err); + return res.sendStatus(403); + } + + console.log('Authenticated user:', user); + req.user = user; + next(); + }); } module.exports = authenticateToken; diff --git a/server/functions/api/routes/avatar.js b/server/functions/api/routes/avatar.js new file mode 100644 index 0000000..4995f11 --- /dev/null +++ b/server/functions/api/routes/avatar.js @@ -0,0 +1,60 @@ +const express = require('express'); +const router = express.Router(); +const authenticateToken = require('../middleware/authenticateToken'); +const User = require('../models/User'); + +// Get current avatar +router.get('/', authenticateToken, async (req, res) => { + try { + console.log('Fetching avatar for userId:', req.user.userId); + const user = await User.findOne({ userId: req.user.userId }).select('avatar'); + + if (!user) { + console.error('User not found for userId:', req.user.userId); + return res.status(404).json({ error: 'User not found' }); + } + + console.log('Avatar fetched successfully:', user.avatar); + res.json(user.avatar); + } catch (error) { + console.error('Error fetching avatar:', error); + res.status(500).json({ error: 'Internal server error', details: error.message }); + } +}); + +// Update avatar +router.put('/', authenticateToken, async (req, res) => { + try { + const { shirtId } = req.body; + + const user = await User.findOne({ userId: req.user.userId }); + if (!user) { + console.error('User not found for userId:', req.user.userId); + return res.status(404).json({ error: 'User not found' }); + } + + if (shirtId) { + if (!mongoose.Types.ObjectId.isValid(shirtId)) { + console.error('Invalid shirt ID:', shirtId); + return res.status(400).json({ error: 'Invalid shirt ID' }); + } + + // Check if the shirt is in inventory + if (!user.inventory.includes(shirtId)) { + console.error('Shirt not in user inventory:', shirtId); + return res.status(400).json({ error: 'Shirt not in user inventory' }); + } + + user.avatar.shirtId = shirtId; + } + + await user.save(); + console.log('Avatar updated successfully for userId:', req.user.userId); + res.json({ message: 'Avatar updated successfully' }); + } catch (error) { + console.error('Error updating avatar:', error); + res.status(500).json({ error: 'Internal server error', details: error.message }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/server/functions/api/routes/pages.js b/server/functions/api/routes/pages.js index fbe173c..470d005 100644 --- a/server/functions/api/routes/pages.js +++ b/server/functions/api/routes/pages.js @@ -70,6 +70,11 @@ router.get('/my/friends', (req, res) => sendHtmlFile(res, 'pages/my/friends.html') ); +// avatar +router.get('/my/avatar', (req, res) => + sendHtmlFile(res, 'pages/my/avatar.html') +); + router.get('/friends/showfriends', (req, res) => sendHtmlFile(res, 'pages/friends/showfriends.html') ); diff --git a/server/functions/api/routes/shirt.js b/server/functions/api/routes/shirt.js index 38346ac..b35321d 100644 --- a/server/functions/api/routes/shirt.js +++ b/server/functions/api/routes/shirt.js @@ -173,6 +173,8 @@ router.post( }) .promise(); + const isForSale = parseInt(price) > 0 ? 1 : 0; + const shirt = new Asset({ assetId: shirtassetId, FileLocation: shirtassetLocation, @@ -181,9 +183,8 @@ router.post( Name: filter.clean(title), Description: filter.clean(description), ThumbnailLocation: thumbnailUrl, - IsForSale: 0, Price: parseInt(price), - IsForSale: 1, //why is there 2 isforsale? + IsForSale: isForSale, //why is there 2 isforsale? Sales: 0, IsPublicDomain: 0, }); @@ -354,15 +355,45 @@ router.get('/user/id/:id', authenticateToken, async (req, res) => { } }); + + router.get('/user', authenticateToken, async (req, res) => { try { - const shirts = await Asset.find({ - creator: req.user.userId, - AssetType: 'Shirt', - }).sort({ updatedAt: -1 }); - res.json(shirts); + console.log('Fetching shirts for userId:', req.user.userId); + const user = await User.findOne({ userId: req.user.userId }).populate('inventory'); + + if (!user) { + console.error('User not found for userId:', req.user.userId); + return res.status(404).json({ error: 'User not found' }); + } + + // Fetch shirts created by user + const createdShirts = await Asset.find({ + creator: user._id, // Using ObjectId from User model + AssetType: 'Shirt', + }).populate('creator', 'username'); + + console.log('Created shirts found:', createdShirts.length); + + // Fetch shirts owned by user + const ownedShirts = await Asset.find({ + _id: { $in: user.inventory }, + AssetType: 'Shirt', + }).populate('creator', 'username'); + + console.log('Owned shirts found:', ownedShirts.length); + + // combine and remove duplicates + const allShirts = [...createdShirts, ...ownedShirts]; + const uniqueShirts = Array.from( + new Set(allShirts.map((s) => s._id.toString())) + ).map((_id) => allShirts.find((s) => s._id.toString() === _id)); + + console.log('Total shirts:', uniqueShirts.length); + res.json(uniqueShirts); } catch (error) { - res.status(500).json({ error: error.message }); + console.error('Error fetching user shirts:', error); + res.status(500).json({ error: 'Internal server error', details: error.message }); } }); diff --git a/server/server.js b/server/server.js index 42fcf8e..873acaa 100644 --- a/server/server.js +++ b/server/server.js @@ -28,6 +28,8 @@ const userRoutes = require('./functions/api/routes/user'); const catalogRoutes = require('./functions/api/routes/catalog'); const forumRoutes = require('./functions/api/routes/forum'); +const avatarRoutes = require('./functions/api/routes/avatar'); + // Init Expressjs const app = express(); const port = process.env.PORT || 3000; @@ -180,6 +182,9 @@ app.use('/api', currencyRoutes); app.use('/api', searchUsersRoutes); app.use('/api/catalog', catalogRoutes); + +app.use('/api/avatar', avatarRoutes); + app.use('/api', userRoutes); app.use('/video', express.static(path.join(__dirname, '../video')));