cc

 avatar
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