cc
unknown
plain_text
a year ago
24 kB
13
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