cc
unknown
plain_text
a year ago
24 kB
7
Indexable
import React, { useState, useEffect } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faUser, faBookmark, faEye, faTag, faPrint, faBox } from "@fortawesome/free-solid-svg-icons"; import { faStar as fasStar } from "@fortawesome/free-solid-svg-icons"; import { faStar as farStar } from "@fortawesome/free-regular-svg-icons"; import { useViewHistory } from "../context/ViewHistoryContext"; import { useFavoriteBooks } from "../context/FavoriteBooksContext"; import { faClock } from '@fortawesome/free-solid-svg-icons'; import Navbar from "../components/Navbar"; import Sidebar from "../components/Sidebar"; import Loader from "../components/Loader"; import { BASE_URL } from '../pages/link'; import { jwtDecode } from 'jwt-decode'; // Ensure correct import for jwt-decode import './BookDetail.css'; const BookDetail = () => { const { id } = useParams(); const navigate = useNavigate(); const { addToViewHistory } = useViewHistory(); const { favoriteBooks, addToFavoriteBooks, removeFromFavoriteBooks } = useFavoriteBooks(); const [review, setReview] = useState(""); const [rating, setRating] = useState(0); const [hoverRating, setHoverRating] = useState(0); const [book, setBook] = useState(null); const [recommendedBooks, setRecommendedBooks] = useState([]); const [submittedReview, setSubmittedReview] = useState(null); const [reviews, setReviews] = useState([]); const [error, setError] = useState(null); const [averageRating, setAverageRating] = useState(0); // const [userId, setUserId] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); const [isSubmittingReview, setIsSubmittingReview] = useState(false); const [userFavoriteBooks, setUserFavoriteBooks] = useState([]); useEffect(() => { const token = localStorage.getItem('token'); const userId = localStorage.getItem('userId'); if (token) { const decodedToken = jwtDecode(token); if (decodedToken && decodedToken.sub) { // setUserId(decodedToken.sub); setIsAuthenticated(true); } } }, []); useEffect(() => { const fetchFavoriteBooks = async () => { try { const token = localStorage.getItem('token'); const userId = localStorage.getItem('userId'); // Kiểm tra nếu không có token, không thực hiện yêu cầu if (!token || !userId) { console.log('User is not authenticated or token/userId not found'); return; } const response = await fetch(`${BASE_URL}/api/public/favorites/list/${userId}`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, 'ngrok-skip-browser-warning': '69420' } }); if (!response.ok) { throw new Error(`Failed to fetch favorite books: ${response.statusText}`); } const favoriteBooksData = await response.json(); setUserFavoriteBooks(favoriteBooksData); } catch (error) { console.error("Error fetching favorite books:", error); setError(`Error fetching favorite books: ${error.message}`); } }; if (isAuthenticated) { fetchFavoriteBooks(); } }, [isAuthenticated]); useEffect(() => { if (!id) { setError("Book ID is missing"); return; } const fetchData = async () => { try { const bookResponse = await fetch(`${BASE_URL}/api/public/books/getById/${id}`, { method: 'GET', headers: { 'ngrok-skip-browser-warning': '69420' } }); if (!bookResponse.ok) { throw new Error(`Failed to fetch book data: ${bookResponse.statusText}`); } const bookData = await bookResponse.json(); setBook(bookData); const reviewResponse = await fetch(`${BASE_URL}/api/public/review/allReview/${id}/reviews`, { method: 'GET', headers: { 'ngrok-skip-browser-warning': '69420' } }); if (!reviewResponse.ok) { throw new Error(`Failed to fetch reviews data: ${reviewResponse.statusText}`); } let reviewsData = []; try { reviewsData = await reviewResponse.json(); if (!Array.isArray(reviewsData)) { throw new Error('reviewsData is not a valid array'); } } catch (jsonError) { console.error("Error parsing reviews data:", jsonError); } setReviews(reviewsData); const averageRatingResponse = await fetch(`${BASE_URL}/api/public/review/ave-rat/${id}/average-rating`, { method: 'GET', headers: { 'ngrok-skip-browser-warning': '69420' } }); if (!averageRatingResponse.ok) { throw new Error(`Failed to fetch average rating data: ${averageRatingResponse.statusText}`); } const averageRatingData = await averageRatingResponse.json(); setAverageRating(averageRatingData.averageRating || 0); const categoriesResponse = await fetch(`${BASE_URL}/api/public/categories/getall`, { method: 'GET', headers: { 'ngrok-skip-browser-warning': '69420' } }); if (!categoriesResponse.ok) { throw new Error(`Failed to fetch categories data: ${categoriesResponse.statusText}`); } const categoriesData = await categoriesResponse.json(); const category = categoriesData.find(cat => cat.name.toLowerCase() === bookData.category.name.toLowerCase()); if (category) { const categoryId = category.id; const categoryBooksResponse = await fetch(`${BASE_URL}/api/public/categories/get/${categoryId}`, { method: 'GET', headers: { 'ngrok-skip-browser-warning': '69420' } }); if (!categoryBooksResponse.ok) { const errorText = await categoryBooksResponse.text(); throw new Error(`Cannot fetch books by category: ${categoryBooksResponse.statusText} - ${errorText}`); } const categoryBooksData = await categoryBooksResponse.json(); setRecommendedBooks(categoryBooksData.books); } else { console.error("Cannot find a matching category for the book title"); } } catch (error) { console.error("Error fetching data:", error); setError(`Error fetching data: ${error.message}`); } }; fetchData(); }, [id, isAuthenticated]); const handleReviewChange = (event) => { setReview(event.target.value); }; const handleStarClick = (star) => { setRating(star); }; const handleReadBookClick = () => { if (book.pdfLink) { addToViewHistory(book); window.open(book.pdfLink, '_blank'); } else { alert("This book's PDF link is not available yet."); } }; const toggleFavorite = async () => { if (!isAuthenticated) { alert("Please log in to add this book to your favorites."); return; } if (isBookFavorite()) { await removeFavoriteBook(); } else { await addFavoriteBook(); } }; const isBookFavorite = () => { return userFavoriteBooks.some((favBook) => favBook.bookId === book.id); }; const addFavoriteBook = async () => { const token = localStorage.getItem('token'); const userId = localStorage.getItem('userId'); if (!token || !userId) { alert("Please log in to add this book to your favorites."); return; } try { const response = await fetch(`${BASE_URL}/api/public/favorites/add?memberId=${userId}&bookId=${book.id}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'ngrok-skip-browser-warning': '69420' } }); if (!response.ok) { throw new Error(`Failed to add book to favorites: ${response.statusText}`); } addToFavoriteBooks(book); setUserFavoriteBooks((prevFavorites) => [...prevFavorites, book]); } catch (error) { setError(`Error adding book to favorites: ${error.message}`); } }; const removeFavoriteBook = async () => { const token = localStorage.getItem('token'); const userId = localStorage.getItem('userId'); if (!token || !userId) { alert("Please log in to remove this book from your favorites."); return; } try { const response = await fetch(`${BASE_URL}/api/public/favorites/remove/{memberId}?memberId=${userId}&bookId=${book.id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'ngrok-skip-browser-warning': '69420' } }); if (!response.ok) { throw new Error(`Failed to remove book from favorites: ${response.statusText}`); } removeFromFavoriteBooks(book.id); setUserFavoriteBooks((prevFavorites) => prevFavorites.filter(favBook => favBook.id !== book.id)); } catch (error) { setError(`Error removing book from favorites: ${error.message}`); } }; const handleRecommendedBookClick = (bookId) => { navigate(`/book/${bookId}`); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const handleSubmitReview = async () => { // Kiểm tra nếu người dùng đã đăng nhập if (!isAuthenticated) { alert("Please log in to submit a review."); navigate('/login'); return; } // Lấy token và userId từ localStorage const token = localStorage.getItem('token'); const userId = localStorage.getItem('userId'); // Kiểm tra nếu không có token, không thực hiện yêu cầu và yêu cầu đăng nhập if (!token || !userId) { alert("Please log in to submit a review."); // navigate('/login'); return; } setIsSubmittingReview(true); try { const decodedToken = jwtDecode(token); const userName = decodedToken.sub; const response = await fetch(`${BASE_URL}/api/member/review/addReview/${id}/reviews?bookId=${id}&createBy=${userId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'ngrok-skip-browser-warning': '69420' }, body: JSON.stringify({ userName: userName, rating: rating, comment: review }) }); if (!response.ok) { throw new Error(`Failed to submit review: ${response.statusText}`); } // Xử lý kết quả đánh giá đã gửi const submittedReview = await response.json(); setSubmittedReview(submittedReview); setReviews([...reviews, submittedReview]); setReview(""); setRating(0); window.location.reload(); // Cập nhật giao diện } catch (error) { setError(`Error submitting review: ${error.message}`); } finally { setIsSubmittingReview(false); } }; const handleMouseEnter = (star) => { setHoverRating(star); }; const handleMouseLeave = () => { setHoverRating(0); }; if (error) { return <div className="error-message">{error}</div>; } if (!book) { return <Loader />; } return ( <div className="book-detail"> <Navbar /> <div className="content"> <Sidebar /> <div className="main"> <div className="main-content"> <div className="book-detail"> <div className="book-left-column"> <div className="book-image"> <img src={book.cover} alt={book.title} /> </div> <div className="book-actions"> <button className="read-button" onClick={handleReadBookClick}> Read Book </button> <button className={`favorite-button${isBookFavorite() ? " favorited" : ""}`} onClick={toggleFavorite} > <FontAwesomeIcon icon={faBookmark} /> <span className="button-text"> {isBookFavorite() ? "Remove Favorites" : "Add to Favorites"} </span> </button> </div> </div> <div className="book-infos"> <h1>{book.title}</h1> <div className="supplier"> <p><strong className="label"><FontAwesomeIcon icon={faUser} />:</strong> {book.author.fullName || "Loading..."}</p> <p><strong className="label"><FontAwesomeIcon icon={faTag} />:</strong> {book.category.name || "Loading..."}</p> </div> <div className="supplier"> <p><strong className="label"><FontAwesomeIcon icon={faBox} />:</strong> {book.supplier.name || "Loading..."}</p> <p><strong className="label"><FontAwesomeIcon icon={faPrint} />:</strong> {book.publisher.name || "Loading..."}</p> </div> <div className="supplier"> <p className="label"><FontAwesomeIcon icon={faEye} className="label" /> <strong className="label">:</strong></p> <p className="rating"> <strong> {[...Array(5)].map((_, index) => ( <FontAwesomeIcon key={index} icon={index < averageRating ? fasStar : farStar} /> ))} </strong> </p> </div> <div className="book-about"> <h2>Description</h2> <div className="book-description-container"> <p>{book.description}</p> </div> </div> </div> </div> <div className="book-additional-info"> <div className="recommended-books"> <h2>Recommended Books</h2> <div className="recommended-books-list"> {recommendedBooks.length > 0 ? ( recommendedBooks.map(recommendedBook => ( <div key={recommendedBook.id} className="recommended-book-item" onClick={() => handleRecommendedBookClick(recommendedBook.id)}> <img src={recommendedBook.cover} alt={recommendedBook.title} /> <div className="recommended-book-info"> <h3>{recommendedBook.title}</h3> </div> </div> )) ) : ( <p>No recommended books available.</p> )} </div> </div> <div className="review-section"> <h2>Review / Rating</h2> <div className="rating-stars"> {[...Array(5)].map((_, index) => { const starRating = index + 1; return ( <FontAwesomeIcon key={starRating} icon={starRating <= (hoverRating || rating) ? fasStar : farStar} onClick={() => handleStarClick(starRating)} onMouseEnter={() => handleMouseEnter(starRating)} onMouseLeave={handleMouseLeave} /> ); })} </div> <textarea placeholder="Write your review..." value={review} onChange={handleReviewChange} disabled={!isAuthenticated} /> <button className="submit-review-button" onClick={handleSubmitReview} disabled={!isAuthenticated || isSubmittingReview} > Submit Review </button> {!isAuthenticated && ( <p className="login-message">Please log in to submit a review.</p> )} {submittedReview && ( <div className="submitted-review-box"> <h3>Your Review:</h3> <div className="submitted-review"> <div className="review-stars"> {[...Array(5)].map((_, index) => ( <FontAwesomeIcon key={index} icon={index < submittedReview.rating ? fasStar : farStar} /> ))} </div> <p>{submittedReview.comment}</p> </div> </div> )} {reviews.length > 0 && ( <div className="existing-reviews"> <h2>Existing Reviews</h2> {reviews.map((review) => ( <div key={review.id} className="review"> <p className="review-id">{review.username}</p> <div className="review-stars"> {[...Array(5)].map((_, index) => ( <FontAwesomeIcon key={index} icon={index < review.rating ? fasStar : farStar} /> ))} </div> <p className="review-text">{review.comment}</p> <p> <FontAwesomeIcon className='icon' icon={faClock} />: {new Date(review.createdAt).toLocaleDateString('vi-VN', { day: '2-digit', month: '2-digit', year: 'numeric' })} </p> </div> ))} </div> )} </div> </div> </div> </div> <button className="back-button" onClick={() => { navigate("/books"); window.scrollTo({ top: 0, behavior: 'smooth' }); }}> Back </button> </div> </div> ); }; export default BookDetail;
Editor is loading...
Leave a Comment