From 412c5ff8f990d3171717f1b3a4d253db59caac73 Mon Sep 17 00:00:00 2001 From: ShiwenFang Date: Wed, 17 Apr 2024 02:40:12 -0400 Subject: [PATCH] outfit archive pages an outfit detail pages linked to db --- back-end/db.js | 31 ++++++- back-end/server.js | 128 ++++++++++++++++++++++---- front-end/src/screens/Archive.js | 39 ++++---- front-end/src/screens/OutfitDetail.js | 62 ++++++++----- front-end/src/styles/OutfitDetail.css | 20 +++- 5 files changed, 216 insertions(+), 64 deletions(-) diff --git a/back-end/db.js b/back-end/db.js index ce7d453..86c5a82 100644 --- a/back-end/db.js +++ b/back-end/db.js @@ -100,6 +100,35 @@ const clothing_item = new mongoose.Schema({ }, {collection: 'clothing'}); const Clothes = mongoose.model("Clothing", clothing_item); -export default {User, Clothes}; + +const outfitSchema = new mongoose.Schema({ + outfitName: { + type: String, + required: true + }, + outfitNotes: { + type: String, + required: false + }, + items: [{ + itemName: { + type: String, + required: true + }, + itemType: { + type: String, + required: true + }, + _id: false // Prevents Mongoose from creating an _id for each subdocument + }], + user: { + type: String, + required: true + } + }, { collection: 'outfit' }); + + const Outfit = mongoose.model("Outfit", outfitSchema); + +export default {User, Clothes, Outfit}; diff --git a/back-end/server.js b/back-end/server.js index dc5b43e..f93e5e8 100644 --- a/back-end/server.js +++ b/back-end/server.js @@ -450,9 +450,48 @@ server.get('/accessories', auth, async (req, res) => { } }); -server.get('/outfits', auth, (req, res) => { - return res.json(outfits); -}) +// server.get('/outfits', auth, (req, res) => { +// return res.json(outfits); +// }) + +server.get('/outfits', auth, async (req, res) => { + try { + const userId = req.user.id.toString(); // Assuming user ID is in the user object + const outfits = await Outfit.find({ user: userId }); // Fetch outfits for the user + + if (!outfits.length) { + return res.status(404).json({ message: 'No outfits found for this user.' }); + } + + // Initialize a promise array to handle multiple asynchronous operations + const outfitsWithImageLinks = await Promise.all(outfits.map(async (outfit) => { + // Map over items in each outfit to fetch corresponding clothes + const imageLinks = await Promise.all(outfit.items.map(async (item) => { + const clothingItem = await Clothes.findOne({ + user: userId, + nameItem: item.itemName, + articleType: item.itemType + }).select('imgLink'); // Assuming 'imgLink' is the field name in Clothes schema + + return clothingItem ? clothingItem.imgLink : null; + })); + + // Filter out any null values if an item wasn't found + const filteredImageLinks = imageLinks.filter(link => link !== null); + + return { + outfitName: outfit.outfitName, + //outfitNotes: outfit.outfitNotes, + imageLinks: filteredImageLinks + }; + })); + + res.json(outfitsWithImageLinks); + } catch (error) { + console.error('Server error when fetching outfits:', error); + res.status(500).json({ message: 'Internal Server Error' }); + } +}); server.get('/verify_login', auth, (req,res) => { return res.json({'loggedIn': true}) @@ -462,15 +501,15 @@ const listener = server.listen(port, function () { console.log(`Server running on port: ${port}`) }) -const findItemByName = (itemName) => { - // Combine all items into a single array - const allItems = [...shirts, ...pants, ...skirts, ...jackets, ...shoes, ...accessories]; +// const findItemByName = (itemName) => { +// // Combine all items into a single array +// const allItems = [...shirts, ...pants, ...skirts, ...jackets, ...shoes, ...accessories]; - // Find the item by name (case-insensitive comparison) - const item = allItems.find(item => item.name.toLowerCase() === itemName.toLowerCase()); +// // Find the item by name (case-insensitive comparison) +// const item = allItems.find(item => item.name.toLowerCase() === itemName.toLowerCase()); - return item; // This will be the item if found, or undefined if not found -}; +// return item; // This will be the item if found, or undefined if not found +// }; // server.get('/item-detail/:itemName', (req, res) => { // try { @@ -532,20 +571,58 @@ server.delete('/delete-item/:itemName', auth, async (req, res) => { } }); -const findOutfitByName = (outfitName) => { - // Assuming you have an array of outfits - const outfit = outfits.find(outfit => outfit.outfitName.toLowerCase() === outfitName.toLowerCase()); - return outfit; -}; +// const findOutfitByName = (outfitName) => { +// // Assuming you have an array of outfits +// const outfit = outfits.find(outfit => outfit.outfitName.toLowerCase() === outfitName.toLowerCase()); +// return outfit; +// }; -server.get('/outfit-detail/:outfitName', (req, res) => { +// server.get('/outfit-detail/:outfitName', (req, res) => { +// try { +// const { outfitName } = req.params; +// const decodedName = decodeURIComponent(outfitName); +// const outfit = findOutfitByName(decodedName); + +// if (outfit) { +// res.json(outfit); +// } else { +// res.status(404).json({ message: 'Outfit not found' }); +// } +// } catch (error) { +// console.error('Server error when fetching outfit details:', error); +// res.status(500).json({ message: 'Internal Server Error' }); +// } +// }); + +server.get('/outfit-detail/:outfitName', auth, async (req, res) => { try { const { outfitName } = req.params; const decodedName = decodeURIComponent(outfitName); - const outfit = findOutfitByName(decodedName); + const userId = req.user.id.toString(); // Assuming you're using the user's id stored in req.user by your authentication middleware + + // Find the outfit by name and user + const outfit = await Outfit.findOne({ + outfitName: decodedName, + user: userId + }).exec(); if (outfit) { - res.json(outfit); + // Now find all the items that are listed in the outfit's items + const itemsPromises = outfit.items.map(item => + Clothes.findOne({ + nameItem: item.itemName, + articleType: item.itemType, + user: userId + }).exec() + ); + + // Execute all the promises to get the items' details + const itemsDetails = await Promise.all(itemsPromises); + + // Filter out any null results in case some items weren't found + const items = itemsDetails.filter(item => item !== null); + + res.json({ outfit, items }); // Send both outfit details and items } else { res.status(404).json({ message: 'Outfit not found' }); } @@ -555,6 +632,21 @@ server.get('/outfit-detail/:outfitName', (req, res) => { } }); +server.delete('/outfit-detail/:outfitName', auth, async (req, res) => { + try { + const decodedName = decodeURIComponent(req.params.outfitName); + const userId = req.user.id.toString() + await Outfit.findOneAndDelete({ + outfitName: decodedName, + user: userId + }); + res.json({ message: 'Outfit deleted successfully' }); + } catch (error) { + console.error('Server error when deleting outfit:', error); + res.status(500).json({ message: 'Internal Server Error' }); + } +}); + // Set up the storage configuration for multer const storage = multer.diskStorage({ destination: (req, file, cb) => { diff --git a/front-end/src/screens/Archive.js b/front-end/src/screens/Archive.js index 2476b21..70d5bd9 100644 --- a/front-end/src/screens/Archive.js +++ b/front-end/src/screens/Archive.js @@ -9,29 +9,22 @@ const Archive = () => { const [loginWarning, setLoginWarning] = useState(false); useEffect(() => { - // Fetch the outfits from your server - const token = localStorage.getItem('token'); - const config = { + const fetchOutfits = async () => { + const token = localStorage.getItem('token'); + try { + const response = await axios.get('http://localhost:3001/outfits', { headers: { Authorization: `Bearer ${token}` } - }; - // fetch('http://localhost:3001/outfits', config) - // .then(response => response.json()) - // .then(data => setOutfits(data)) - // .catch(error => { - // console.log(error); - // setLoginWarning(true); - // } - // ); - axios.get('http://localhost:3001/outfits', config) - .then( res => { - setOutfits(res.data) - }) - .catch((e) => { - console.log(e) - setLoginWarning(true); - }) + }); + setOutfits(response.data); // Assume response.data is the array of outfits with image links + } catch (error) { + console.error('Error fetching outfits:', error); + setLoginWarning(true); + } + }; + + fetchOutfits(); }, []); return ( @@ -45,10 +38,10 @@ const Archive = () => { {outfits.map((outfit) => ( // Removed index, using outfitName as key
- {outfit.items.map((item) => ( // Removed itemIndex, key will be provided by the Link component - + {outfit.imageLinks.map((imgLink) => ( // Removed itemIndex, key will be provided by the Link component +
- {item.name} + {outfit.outfitName}
))} diff --git a/front-end/src/screens/OutfitDetail.js b/front-end/src/screens/OutfitDetail.js index 05e7c29..5d164b1 100644 --- a/front-end/src/screens/OutfitDetail.js +++ b/front-end/src/screens/OutfitDetail.js @@ -2,36 +2,55 @@ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import OverlayMenu from '../components/OverlayMenu'; import '../styles/OutfitDetail.css'; +import axios from 'axios'; const OutfitDetail = () => { const { outfitName } = useParams(); const navigate = useNavigate(); - const [outfit, setOutfit] = useState(null); + const [outfitDetails, setOutfitDetails] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const encodedOutfitName = encodeURIComponent(outfitName); - fetch(`http://localhost:3001/outfit-detail/${encodedOutfitName}`) - .then(response => { - if (!response.ok) throw new Error('Network response was not ok'); - return response.json(); - }) - .then(data => { - setOutfit(data); - setLoading(false); - }) - .catch(error => { - setError(error.toString()); - setLoading(false); - }); + const token = localStorage.getItem('token'); + axios.get(`http://localhost:3001/outfit-detail/${encodedOutfitName}`, { + headers: { + Authorization: `Bearer ${token}` + } + }) + .then(response => { + setOutfitDetails(response.data); + setLoading(false); + }) + .catch(error => { + setError(error.toString()); + setLoading(false); + }); }, [outfitName]); const handleBackClick = () => navigate(-1); if (loading) return
Loading...
; if (error) return
Error: {error}
; - if (!outfit) return
Outfit not found
; + if (!outfitDetails) return
Outfit not found
; + + const handleDeleteOutfit = async () => { + try { + const encodedOutfitName = encodeURIComponent(outfitName); + const token = localStorage.getItem('token'); + await axios.delete(`http://localhost:3001/outfit-detail/${encodedOutfitName}`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + alert('Outfit deleted successfully'); + navigate('/'); // or navigate to the appropriate page after deletion + } catch (error) { + setError('Error deleting outfit: ' + error.response.data.message); + } + }; + return (
@@ -41,22 +60,23 @@ const OutfitDetail = () => {

Outfit Details

- {outfit.items.map(item => ( - + {outfitDetails.items.map((item) => ( +
- {item.name} + {item.itemName}
-

{item.name}

+

{item.itemName}

Brand: {item.brand}

Type: {item.type}

))} -

{outfit.outfitName}

-

{outfit.notes}

+

{outfitDetails.outfit.outfitName}

+

{outfitDetails.outfit.outfitNotes}

+
); }; diff --git a/front-end/src/styles/OutfitDetail.css b/front-end/src/styles/OutfitDetail.css index 3f0e7e3..3bc57b2 100644 --- a/front-end/src/styles/OutfitDetail.css +++ b/front-end/src/styles/OutfitDetail.css @@ -61,4 +61,22 @@ .OutfitDetail-item-link { text-decoration: none; /* Removes underline from links */ color: inherit; /* Keeps text color consistent with the rest of the item */ -} \ No newline at end of file +} +.OutfitDetail-deleteButton { + position: fixed; /* Fixed position */ + bottom: 25px; /* Distance from the bottom */ + left: 25px; /* Distance from the right */ + padding: 10px 20px; + font-family: 'Cormorant Infant', serif; + font-size: 20px; + color: #000; /* Black text */ + background-color: transparent; /* Transparent background */ + border: none; /* No border */ + text-decoration: underline; /* Underline text */ + cursor: pointer; + } + + .OutfitDetail-deleteButton:hover { + text-decoration: none; /* Remove underline on hover for effect */ + } + \ No newline at end of file