Files
scoffer/frontend/src/components/RecipeModal.tsx
2025-08-10 14:38:04 +01:00

238 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
interface RecipeModalProps {
recipe: Recipe;
onClose: () => void;
onAddToSelection: (recipeId: string) => void;
onRecipeDeleted?: (recipeId: string) => void;
isSelected: boolean;
selectedQuantity: number;
}
const RecipeModal: React.FC<RecipeModalProps> = ({
recipe,
onClose,
onAddToSelection,
onRecipeDeleted,
isSelected,
selectedQuantity,
}) => {
const { user } = useAuth();
const navigate = useNavigate();
const [isDeleting, setIsDeleting] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};
const handleDeleteClick = () => {
setShowDeleteConfirm(true);
};
const handleDeleteConfirm = async () => {
setIsDeleting(true);
try {
await recipesAPI.delete(recipe._id);
onRecipeDeleted?.(recipe._id);
onClose();
} catch (error) {
console.error('Error deleting recipe:', error);
alert('Failed to delete recipe. Please try again.');
} finally {
setIsDeleting(false);
setShowDeleteConfirm(false);
}
};
const handleDeleteCancel = () => {
setShowDeleteConfirm(false);
};
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) {
case 'easy': return '#4CAF50';
case 'medium': return '#FF9800';
case 'hard': return '#F44336';
default: return '#757575';
}
};
const getCategoryColor = (category: string) => {
switch (category) {
case 'breakfast': return '#FFE082';
case 'lunch': return '#81C784';
case 'dinner': return '#64B5F6';
case 'dessert': return '#F48FB1';
case 'snack': return '#FFB74D';
case 'appetizer': return '#A1C181';
default: return '#E0E0E0';
}
};
return (
<div className="modal-backdrop" onClick={handleBackdropClick}>
<div className="modal-content">
<div className="modal-header">
<button className="close-button" onClick={onClose}>
×
</button>
</div>
<div className="modal-body">
<div className="recipe-image-section">
<img
src={recipe.imageUrl || '/placeholder-recipe.jpg'}
alt={recipe.title}
className="modal-recipe-image"
onError={(e) => {
(e.target as HTMLImageElement).src = '/placeholder-recipe.jpg';
}}
/>
<div className="modal-badges">
<span
className="difficulty-badge"
style={{ backgroundColor: getDifficultyColor(recipe.difficulty) }}
>
{recipe.difficulty}
</span>
<span
className="category-badge"
style={{ backgroundColor: getCategoryColor(recipe.category) }}
>
{recipe.category}
</span>
</div>
</div>
<div className="recipe-details">
<h2 className="modal-recipe-title">{recipe.title}</h2>
<p className="modal-recipe-description">{recipe.description}</p>
<div className="recipe-meta-grid">
<div className="meta-card">
<span className="meta-label">Prep Time</span>
<span className="meta-value">{recipe.prepTime} min</span>
</div>
<div className="meta-card">
<span className="meta-label">Cook Time</span>
<span className="meta-value">{recipe.cookTime} min</span>
</div>
<div className="meta-card">
<span className="meta-label">Total Time</span>
<span className="meta-value">{recipe.prepTime + recipe.cookTime} min</span>
</div>
<div className="meta-card">
<span className="meta-label">Servings</span>
<span className="meta-value">{recipe.servings}</span>
</div>
</div>
<div className="ingredients-section">
<h3>Ingredients</h3>
<ul className="ingredients-list">
{recipe.ingredients.map((ingredient, index) => (
<li key={index} className="ingredient-item">
<span className="ingredient-amount">
{ingredient.amount} {ingredient.unit}
</span>
<span className="ingredient-name">{ingredient.name}</span>
</li>
))}
</ul>
</div>
<div className="instructions-section">
<h3>Instructions</h3>
<ol className="instructions-list">
{recipe.instructions.map((instruction) => (
<li key={instruction.step} className="instruction-item">
<div className="instruction-step">{instruction.step}</div>
<div className="instruction-text">{instruction.description}</div>
</li>
))}
</ol>
</div>
</div>
</div>
<div className="modal-footer">
<div className="modal-footer-buttons">
<div className="modal-action-buttons">
{canEdit && (
<button
className="btn btn-secondary btn-edit"
onClick={handleEditClick}
>
Edit Recipe
</button>
)}
{canDelete && (
<button
className="btn btn-danger btn-delete"
onClick={handleDeleteClick}
disabled={isDeleting}
>
{isDeleting ? 'Deleting...' : 'Delete Recipe'}
</button>
)}
</div>
<button
className={`btn ${isSelected ? 'btn-success' : 'btn-primary'} btn-large`}
onClick={() => onAddToSelection(recipe._id)}
>
{isSelected ? `Added to Menu (${selectedQuantity})` : 'Add to Menu'}
</button>
</div>
</div>
{/* Delete Confirmation Dialog */}
{showDeleteConfirm && (
<div className="delete-confirm-overlay">
<div className="delete-confirm-dialog">
<h3>Delete Recipe</h3>
<p>Are you sure you want to delete "{recipe.title}"?</p>
<p className="delete-warning">
This action cannot be undone. The recipe will be removed from all users' menus.
</p>
<div className="delete-confirm-buttons">
<button
className="btn btn-secondary"
onClick={handleDeleteCancel}
disabled={isDeleting}
>
Cancel
</button>
<button
className="btn btn-danger"
onClick={handleDeleteConfirm}
disabled={isDeleting}
>
{isDeleting ? 'Deleting...' : 'Delete Recipe'}
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default RecipeModal;