source: CookCraft-FrontEnd/CookCraft-FrontEnd-master/cookcraft-app/src/components/RecipesComponents/RecipeCard.jsx

Last change on this file was d7b7f00, checked in by Gorazd Biskoski <gorazdbiskoskii@…>, 4 weeks ago

Add project

  • Property mode set to 100644
File size: 10.6 KB
Line 
1import React, { useState, useEffect } from "react";
2import { useParams, useNavigate, useLocation } from "react-router-dom";
3import { FaArrowLeft, FaHeart, FaTrash } from "react-icons/fa";
4import StarRating from "./StarRating";
5import Modal from "./ReviewModal";
6import styles from "../../css/RecipesCss/recipe-card-style.module.css";
7import ShoppingCart from '../ShoppingCartComponents/ShoppingCart';
8
9const RecipeCard = () => {
10 const { id } = useParams();
11 const navigate = useNavigate();
12 const location = useLocation();
13 const [recipeAndProducts, setRecipeAndProducts] = useState(null);
14 const [loading, setLoading] = useState(true);
15 const [newReview, setNewReview] = useState('');
16 const [rating, setRating] = useState(0);
17 const [reviews, setReviews] = useState([]);
18 const [visibleReviews, setVisibleReviews] = useState(3);
19 const [isFavorite, setIsFavorite] = useState(false);
20 const [hasReviewed, setHasReviewed] = useState(false);
21 const [showModal, setShowModal] = useState(false);
22 const [deleteReviewId, setDeleteReviewId] = useState(null);
23 const [modalMessage, setModalMessage] = useState("");
24
25 const userEmail = localStorage.getItem("email");
26 const token = localStorage.getItem("token");
27
28 useEffect(() => {
29 const fetchRecipe = async () => {
30 try {
31 const response = await fetch(`http://localhost:8080/api/recipes/${id}`);
32 const data = await response.json();
33
34 setRecipeAndProducts(data);
35 setReviews(data.reviews);
36
37
38 const userReview = data.reviews.find(review => review.userEmail === userEmail);
39 setHasReviewed(!!userReview);
40
41 const favoriteResponse = await fetch(`http://localhost:8080/api/favorite/check?userEmail=${userEmail}&recipeId=${id}`);
42 const isFavorited = await favoriteResponse.json();
43 setIsFavorite(isFavorited);
44
45 setLoading(false);
46 window.scrollTo({ top: 0, behavior: "smooth" });
47 } catch (error) {
48 console.error('Error fetching recipe:', error);
49 setLoading(false);
50 }
51 };
52
53 fetchRecipe();
54 }, [id, userEmail]);
55
56 const handleBackClick = () => {
57 if (location.state?.fromMyReviews) {
58 navigate('/profile', { state: { selected: 2 } });
59 } else if(location.state?.fromMyFavoriteRecipes) {
60 navigate('/profile', { state: { selected: 1 } })
61 } else if (location.state?.fromHomepage) {
62 navigate("/");
63 window.scrollTo({ top: 550, behavior: "smooth" });
64 } else if (location.state) {
65 const { category, nationality, page, productIds } = location.state;
66 const searchParams = new URLSearchParams();
67 if (category) searchParams.set('category', category);
68 if (nationality) searchParams.set('nationality', nationality);
69 if (productIds && productIds.length > 0) {
70 productIds.forEach(id => searchParams.append('productId', id));
71 }
72 searchParams.set('page', page || 0);
73 navigate(`/recipes?${searchParams.toString()}`, { state: location.state });
74 } else {
75 navigate("/recipes");
76 }
77 };
78 const handleReviewSubmit = async () => {
79 if (!token) {
80 setModalMessage("You need to log in to submit a review.");
81 setShowModal(true);
82 return;
83 }
84
85 if (hasReviewed) {
86 setModalMessage("You have already submitted a review for this recipe.");
87 setShowModal(true);
88 return;
89 }
90 if (rating === 0){
91 setModalMessage("You cannot leave a rating of 0.");
92 setShowModal(true);
93 return;
94 }
95
96 try {
97 const response = await fetch("http://localhost:8080/api/reviews", {
98 method: "POST",
99 headers: {
100 "Content-Type": "application/json",
101 Authorization: `Bearer ${token}`,
102 },
103 body: JSON.stringify({ recipeId: id, review: newReview, rating }),
104 });
105
106 if (response.ok) {
107 const updatedReviews = await response.json();
108 setReviews(updatedReviews.sort((a, b) => b.rating - a.rating));
109 setNewReview("");
110 setRating(0);
111 setHasReviewed(true);
112
113 } else {
114 console.error("Failed to submit review:", await response.text());
115 setModalMessage("Failed to submit review.");
116 setShowModal(true);
117 }
118 } catch (error) {
119 console.error("Error submitting review:", error);
120 setModalMessage("Error submitting review.");
121 setShowModal(true);
122 }
123 };
124
125 const handleDeleteReview = async () => {
126 if (!token) return;
127
128 try {
129 const response = await fetch(`http://localhost:8080/api/reviews/${deleteReviewId}`, {
130 method: 'DELETE',
131 headers: {
132 Authorization: `Bearer ${token}`,
133 }
134 });
135
136 if (response.ok) {
137 setReviews(reviews.filter(review => review.id !== deleteReviewId));
138 setDeleteReviewId(null);
139 setHasReviewed(false);
140 setShowModal(false);
141 } else {
142 console.error('Error deleting review');
143 setModalMessage("Error deleting review.");
144 setShowModal(true);
145 }
146 } catch (error) {
147 console.error('Error deleting review:', error);
148 setModalMessage("Error deleting review.");
149 setShowModal(true);
150 }
151 };
152 const handleFavoriteClick = async () => {
153 try {
154 const response = await fetch("http://localhost:8080/api/favorite", {
155 method: 'POST',
156 headers: {
157 'Content-Type': 'application/json',
158 },
159 body: JSON.stringify({
160 userEmail: localStorage.getItem("email"),
161 recipeId: id
162 })
163 });
164
165 if (response.ok) {
166 setIsFavorite(!isFavorite);
167 } else {
168 alert("Failed to update favorite status.");
169 }
170 }
171 catch (error) {
172 alert("An error occurred while trying to add the recipe to your favorites.")
173 }
174 };
175
176 const openDeleteModal = (reviewId) => {
177 setDeleteReviewId(reviewId);
178 setModalMessage("Are you sure you want to delete this review?");
179 setShowModal(true);
180 };
181
182 const closeModal = () => {
183 setShowModal(false);
184 setDeleteReviewId(null);
185 };
186
187 if (loading) return <div className={styles.loading}>Loading...</div>;
188 if (!recipeAndProducts) return <div className={styles.noRecipe}>No recipe found.</div>;
189
190 return (
191 <div className={styles.recipeCard}>
192<Modal
193 isOpen={showModal}
194 onClose={closeModal}
195 title="Notification"
196 deleteReviewId={deleteReviewId}
197 handleDeleteReview={handleDeleteReview}
198>
199 <p>{modalMessage}</p>
200</Modal>
201 <div className={styles.backArrow} onClick={handleBackClick}>
202 <FaArrowLeft />
203 </div>
204
205 <button
206 className={`${styles.favoriteButton} ${isFavorite ? styles.favoriteActive : ''}`}
207 onClick={handleFavoriteClick}
208 >
209 <FaHeart />
210 </button>
211<div className={styles.recipeDetails}>
212 <img
213 src={recipeAndProducts.recipe.strMealThumb}
214 alt={recipeAndProducts.recipe.strMeal}
215 className={styles.recipeImage}
216 />
217 <div className={styles.textSection}>
218 <h2>{recipeAndProducts.recipe.strMeal}</h2>
219 <p className={styles.description}>{recipeAndProducts.recipe.strInstructions}</p>
220 </div>
221</div>
222
223 <div className={styles.ingredients}>
224 <h3>Ingredients</h3>
225 <ul>
226 {recipeAndProducts.productsInRecipes.map(product => (
227 <li key={product.id}>{product.name} - {product.measurement}</li>
228 ))}
229 </ul>
230 </div>
231 <ShoppingCart ingredients={recipeAndProducts.productsInRecipes} hideIngredients={false}/>
232 <div className={styles.videoSection}>
233 <div className={styles.videoSection}>
234 <h3>Watch the Recipe</h3>
235 </div>
236 <div className={styles.video}>
237 <iframe
238 src={recipeAndProducts.recipe.strYoutube.replace("watch?v=", "embed/")}
239 title="YouTube video player"
240 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
241 allowFullScreen
242 />
243 </div>
244 </div>
245 <div className={styles.reviewsSection}>
246 <h3>REVIEWS</h3>
247 <ul>
248 {reviews.slice(0, visibleReviews).map(review => (
249 <li key={review.id} className={styles.reviewItem}>
250 <div className={styles.reviewContent}>
251 <strong>{review.userName} {review.userSurname}</strong>
252 <span>{review.review}</span>
253 </div>
254 <span className={styles.ratingText}>Rating: {review.rating}</span>
255 {review.userEmail === userEmail && (
256 <button onClick={() => openDeleteModal(review.id)} className={styles.deleteButton}>
257 <FaTrash /> Delete
258 </button>
259 )}
260 </li>
261
262 ))}
263</ul>
264 {visibleReviews < reviews.length && (
265 <button onClick={() => setVisibleReviews(prev => prev + 3)} className={styles.showMoreButton}>
266 Show More
267 </button>
268 )}
269
270 <div className={styles.addReview}>
271 <h4>Add Your Review</h4>
272 <textarea
273 value={newReview}
274 onChange={(e) => setNewReview(e.target.value)}
275 placeholder="Write your review here..."
276 />
277 <StarRating rating={rating} setRating={setRating} />
278 <button
279 onClick={handleReviewSubmit}
280 className={styles.submitReviewButton}
281 >
282 Submit Review
283 </button>
284 </div>
285 </div>
286 </div>
287 );
288};
289
290export default RecipeCard;
Note: See TracBrowser for help on using the repository browser.