238 lines
7.7 KiB
TypeScript
238 lines
7.7 KiB
TypeScript
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;
|