Spaces:
Sleeping
Sleeping
Download frontend/src/components/BookDetailsModal.js from vishalharkal/BookMind: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/spaces/vishalharkal/BookMind/resolve/main/frontend/src/components/BookDetailsModal.js
- Command line
-
hf download hf://spaces/vishalharkal/BookMind/frontend/src/components/BookDetailsModal.js
-
curl -L -o BookDetailsModal.js https://huggingface.co/spaces/vishalharkal/BookMind/resolve/main/frontend/src/components/BookDetailsModal.js
11.5 kB
| import React, { useState } from 'react'; | |
| import { X, Star, Heart, Share2, Calendar, Building, User, BookOpen, TrendingUp } from 'lucide-react'; | |
| const BookDetailsModal = ({ book, onClose, onRecommend }) => { | |
| const [isFavorite, setIsFavorite] = useState(false); | |
| const [rating, setRating] = useState(0); | |
| const [hoverRating, setHoverRating] = useState(0); | |
| const handleAddToFavorites = () => { | |
| setIsFavorite(!isFavorite); | |
| // Save to localStorage | |
| const favorites = JSON.parse(localStorage.getItem('bookFavorites') || '[]'); | |
| if (!isFavorite) { | |
| favorites.push(book); | |
| localStorage.setItem('bookFavorites', JSON.stringify(favorites)); | |
| } else { | |
| const filtered = favorites.filter(fav => fav.title !== book.title); | |
| localStorage.setItem('bookFavorites', JSON.stringify(filtered)); | |
| } | |
| }; | |
| const handleShare = () => { | |
| const shareText = `Check out "${book.title}" by ${book.author} on BookMind! 📚`; | |
| if (navigator.share) { | |
| navigator.share({ | |
| title: book.title, | |
| text: shareText, | |
| url: window.location.href, | |
| }); | |
| } else { | |
| // Fallback: Copy to clipboard | |
| navigator.clipboard.writeText(shareText); | |
| alert('Recommendation copied to clipboard!'); | |
| } | |
| }; | |
| const handleRating = (value) => { | |
| setRating(value); | |
| // Save rating to localStorage | |
| const ratings = JSON.parse(localStorage.getItem('bookRatings') || '{}'); | |
| ratings[book.title] = value; | |
| localStorage.setItem('bookRatings', JSON.stringify(ratings)); | |
| }; | |
| if (!book) return null; | |
| return ( | |
| <> | |
| {/* Backdrop */} | |
| <div | |
| className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 transition-opacity" | |
| onClick={onClose} | |
| ></div> | |
| {/* Modal */} | |
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto"> | |
| <div | |
| className="bg-white rounded-3xl shadow-2xl max-w-2xl w-full my-8 overflow-hidden animate-in fade-in scale-95" | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| {/* Close Button */} | |
| <button | |
| onClick={onClose} | |
| className="absolute top-4 right-4 z-10 p-2 bg-bg-body rounded-full hover:bg-border transition-all" | |
| > | |
| <X size={24} className="text-text-primary" /> | |
| </button> | |
| {/* Modal Content */} | |
| <div className="p-8"> | |
| {/* Header with Book Image and Info */} | |
| <div className="flex gap-8 mb-8"> | |
| {/* Book Cover */} | |
| <div className="flex-shrink-0"> | |
| <img | |
| src={book.image_url} | |
| alt={book.title} | |
| className="w-40 h-56 object-cover rounded-2xl shadow-lg border-4 border-border" | |
| /> | |
| </div> | |
| {/* Book Details */} | |
| <div className="flex-1"> | |
| <h2 className="text-3xl font-bold text-text-primary mb-2"> | |
| {book.title} | |
| </h2> | |
| <p className="text-xl text-text-secondary mb-6 flex items-center gap-2"> | |
| <User size={18} className="text-[#0891b2]" /> | |
| {book.author} | |
| </p> | |
| {/* Rating Stars */} | |
| <div className="flex items-center gap-3 mb-6"> | |
| <div className="flex gap-1"> | |
| {[1, 2, 3, 4, 5].map((star) => ( | |
| <button | |
| key={star} | |
| onClick={() => handleRating(star)} | |
| onMouseEnter={() => setHoverRating(star)} | |
| onMouseLeave={() => setHoverRating(0)} | |
| className="transition-transform hover:scale-110" | |
| > | |
| <Star | |
| size={24} | |
| className={`${ | |
| star <= (hoverRating || rating) | |
| ? 'fill-yellow-400 text-yellow-400' | |
| : 'text-gray-300' | |
| }`} | |
| /> | |
| </button> | |
| ))} | |
| </div> | |
| <span className="text-sm text-text-secondary"> | |
| {rating > 0 ? `You rated: ${rating}/5` : 'Rate this book'} | |
| </span> | |
| </div> | |
| {/* Book Metadata */} | |
| <div className="space-y-2 mb-6"> | |
| {book.year && ( | |
| <div className="flex items-center gap-3 text-text-secondary"> | |
| <Calendar size={16} className="text-[#0891b2]" /> | |
| <span className="font-medium">{book.year}</span> | |
| </div> | |
| )} | |
| {book.publisher && ( | |
| <div className="flex items-center gap-3 text-text-secondary"> | |
| <Building size={16} className="text-[#0891b2]" /> | |
| <span className="font-medium">{book.publisher}</span> | |
| </div> | |
| )} | |
| </div> | |
| {/* Action Buttons */} | |
| <div className="flex gap-3 flex-wrap"> | |
| <button | |
| onClick={handleAddToFavorites} | |
| className={`flex items-center gap-2 px-4 py-2 rounded-lg font-semibold transition-all ${ | |
| isFavorite | |
| ? 'bg-red-500 text-white' | |
| : 'bg-bg-body text-text-secondary hover:bg-border' | |
| }`} | |
| > | |
| <Heart size={18} fill={isFavorite ? 'currentColor' : 'none'} /> | |
| {isFavorite ? 'Saved' : 'Save'} | |
| </button> | |
| <button | |
| onClick={handleShare} | |
| className="flex items-center gap-2 px-4 py-2 bg-bg-body text-text-secondary rounded-lg font-semibold hover:bg-border transition-all" | |
| > | |
| <Share2 size={18} /> | |
| Share | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Description Section */} | |
| <div className="mb-8"> | |
| <h3 className="text-lg font-bold text-text-primary mb-3 flex items-center gap-2"> | |
| <BookOpen size={20} className="text-[#0891b2]" /> | |
| About This Book | |
| </h3> | |
| <p className="text-text-secondary leading-relaxed bg-bg-body p-4 rounded-xl"> | |
| {book.description || 'No description available for this book.'} | |
| </p> | |
| </div> | |
| {/* Recommendation Methods */} | |
| <div className="mb-6"> | |
| <h3 className="text-lg font-bold text-text-primary mb-4 flex items-center gap-2"> | |
| <TrendingUp size={20} className="text-[#0891b2]" /> | |
| Get Recommendations | |
| </h3> | |
| <div className="grid grid-cols-3 gap-3"> | |
| <button | |
| onClick={() => { | |
| onRecommend(book.title, 'hybrid'); | |
| onClose(); | |
| }} | |
| className="bg-gradient-to-r from-[#06b6d4] to-[#0891b2] text-white px-4 py-3 rounded-lg font-bold text-sm hover:shadow-lg transition-all hover:-translate-y-0.5" | |
| > | |
| Hybrid | |
| </button> | |
| <button | |
| onClick={() => { | |
| onRecommend(book.title, 'collaborative'); | |
| onClose(); | |
| }} | |
| className="bg-gradient-to-r from-[#0369a1] to-[#0284c7] text-white px-4 py-3 rounded-lg font-bold text-sm hover:shadow-lg transition-all hover:-translate-y-0.5" | |
| > | |
| Collaborative | |
| </button> | |
| <button | |
| onClick={() => { | |
| onRecommend(book.title, 'content'); | |
| onClose(); | |
| }} | |
| className="bg-gradient-to-r from-[#0891b2] to-[#06d6a0] text-white px-4 py-3 rounded-lg font-bold text-sm hover:shadow-lg transition-all hover:-translate-y-0.5" | |
| > | |
| Content | |
| </button> | |
| </div> | |
| </div> | |
| {/* Close Button */} | |
| <button | |
| onClick={onClose} | |
| className="w-full bg-text-primary text-white py-3 rounded-lg font-bold hover:bg-text-secondary transition-all" | |
| > | |
| Close | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </> | |
| ); | |
| }; | |
| export default BookDetailsModal; | |