diff --git a/backend/routes/recipes.js b/backend/routes/recipes.js index fb8fcd5..960804e 100644 --- a/backend/routes/recipes.js +++ b/backend/routes/recipes.js @@ -160,19 +160,27 @@ router.post('/', authenticateToken, async (req, res) => { } }); -// Update recipe -router.put('/:id', async (req, res) => { +// Update recipe (protected route) +router.put('/:id', authenticateToken, async (req, res) => { try { - const recipe = await Recipe.findByIdAndUpdate( - req.params.id, - req.body, - { new: true, runValidators: true } - ); - if (!recipe) { + const recipeId = req.params.id; + + // Find the recipe first to preserve original creator + const existingRecipe = await Recipe.findById(recipeId); + if (!existingRecipe) { return res.status(404).json({ error: 'Recipe not found' }); } - res.json(recipe); + + // Update the recipe while preserving the createdBy field + const updatedRecipe = await Recipe.findByIdAndUpdate( + recipeId, + { ...req.body, createdBy: existingRecipe.createdBy }, // Preserve original creator + { new: true, runValidators: true } + ); + + res.json(updatedRecipe); } catch (error) { + console.error('Error updating recipe:', error); res.status(400).json({ error: error.message }); } }); @@ -182,17 +190,12 @@ router.delete('/:id', authenticateToken, async (req, res) => { try { const recipeId = req.params.id; - // Find the recipe first to check ownership + // Find the recipe first to verify it exists const recipe = await Recipe.findById(recipeId); if (!recipe) { return res.status(404).json({ error: 'Recipe not found' }); } - // Check if user owns the recipe (only recipe creator can delete) - if (recipe.createdBy.toString() !== req.userId) { - return res.status(403).json({ error: 'You can only delete recipes you created' }); - } - // Delete the recipe await Recipe.findByIdAndDelete(recipeId); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a8b0078..e1076dc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,6 +5,7 @@ import Dashboard from './pages/Dashboard'; import Login from './pages/Login'; import Register from './pages/Register'; import CreateRecipe from './pages/CreateRecipe'; +import EditRecipe from './pages/EditRecipe'; import './App.css'; // Protected Route component @@ -110,6 +111,14 @@ function App() { } /> + + + + } + /> } /> diff --git a/frontend/src/components/IngredientAutocomplete.tsx b/frontend/src/components/IngredientAutocomplete.tsx index cf55543..892c384 100644 --- a/frontend/src/components/IngredientAutocomplete.tsx +++ b/frontend/src/components/IngredientAutocomplete.tsx @@ -24,6 +24,7 @@ const IngredientAutocomplete: React.FC = ({ const [showSuggestions, setShowSuggestions] = useState(false); const [loading, setLoading] = useState(false); const [selectedIndex, setSelectedIndex] = useState(-1); + const [hasUserInteracted, setHasUserInteracted] = useState(false); const inputRef = useRef(null); const suggestionsRef = useRef(null); @@ -49,7 +50,8 @@ const IngredientAutocomplete: React.FC = ({ const response = await api.get(`/recipes/ingredients/suggestions?q=${encodeURIComponent(query)}`); const suggestionData = response.data as IngredientSuggestion[]; setSuggestions(suggestionData); - setShowSuggestions(suggestionData.length > 0); + // Only show suggestions if user has interacted with the component + setShowSuggestions(hasUserInteracted && suggestionData.length > 0); setSelectedIndex(-1); } catch (error) { console.error('Error fetching ingredient suggestions:', error); @@ -74,6 +76,7 @@ const IngredientAutocomplete: React.FC = ({ const handleInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value; + setHasUserInteracted(true); onChange(newValue); }; @@ -122,7 +125,9 @@ const IngredientAutocomplete: React.FC = ({ }; const handleFocus = () => { - if (value && suggestions.length > 0) { + setHasUserInteracted(true); + // Show suggestions if we have them and user has interacted + if (suggestions.length > 0) { setShowSuggestions(true); } }; diff --git a/frontend/src/components/RecipeModal.css b/frontend/src/components/RecipeModal.css index e1eb05e..9d4515c 100644 --- a/frontend/src/components/RecipeModal.css +++ b/frontend/src/components/RecipeModal.css @@ -231,6 +231,35 @@ justify-content: space-between; } +.modal-action-buttons { + display: flex; + gap: 8px; + align-items: center; +} + +.btn-edit { + background: #6c757d; + color: white; + border: none; + padding: 10px 20px; + border-radius: 8px; + font-size: 0.9rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.btn-edit:hover:not(:disabled) { + background: #5a6268; + transform: translateY(-1px); +} + +.btn-edit:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + .btn-delete { background: #dc3545; color: white; diff --git a/frontend/src/components/RecipeModal.tsx b/frontend/src/components/RecipeModal.tsx index ab1c0f3..45938f4 100644 --- a/frontend/src/components/RecipeModal.tsx +++ b/frontend/src/components/RecipeModal.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Recipe, recipesAPI } from '../services/api'; import { useAuth } from '../context/AuthContext'; import './RecipeModal.css'; @@ -21,6 +22,7 @@ const RecipeModal: React.FC = ({ selectedQuantity, }) => { const { user } = useAuth(); + const navigate = useNavigate(); const [isDeleting, setIsDeleting] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const handleBackdropClick = (e: React.MouseEvent) => { @@ -52,8 +54,15 @@ const RecipeModal: React.FC = ({ setShowDeleteConfirm(false); }; - // Check if current user is the creator of this recipe - const canDelete = user && recipe.createdBy === user.id; + const handleEditClick = () => { + // Navigate to edit page with recipe data + navigate(`/edit-recipe/${recipe._id}`, { state: { recipe } }); + onClose(); + }; + + // Any logged-in user can edit or delete recipes + const canEdit = !!user; + const canDelete = !!user; const getDifficultyColor = (difficulty: string) => { switch (difficulty) { @@ -164,15 +173,25 @@ const RecipeModal: React.FC = ({
- {canDelete && ( - - )} +
+ {canEdit && ( + + )} + {canDelete && ( + + )} +
+
+
+ ); + } + + return ( +
+
+

Edit Recipe

+

Update your recipe details

+
+ +
+ {/* Basic Information */} +
+

Basic Information

+ +
+ + handleInputChange('title', e.target.value)} + placeholder="Enter recipe title" + required + /> +
+ +
+ +