Anda bilang: perhatikan kode ini tugasmu adalah tambahkan fitur Auto-scroll dengan kecepatan yang bisa diatur Toggle fullscreen mode pada kode yang saya berikan dan juga perbaiki tampilan ui/uxnya agar responsive dan lebih menarik dan juga fitur-fitur sebelumnya di kode ini jangan di hapus import { useState, useEffect } from "react"; import { useRouter } from "next/router"; import { db } from "../../../BE/firebase"; import { doc, getDoc, collection, getDocs } from "firebase/firestore"; import Link from "next/link"; import { ChevronLeft, ChevronRight, ZoomIn, ZoomOut, Menu, X, ChevronUp, ChevronDown, BookOpen, List, Home, Share2, Heart } from 'lucide-react'; export default function ChapterDetail() { const router = useRouter(); const { id: comicId, chapterId } = router.query; const [detailKomikId, setDetailKomikId] = useState(null); const [chapter, setChapter] = useState(null); const [chapterList, setChapterList] = useState([]); const [currentIndex, setCurrentIndex] = useState(null); const [isMenuOpen, setIsMenuOpen] = useState(false); const [isChapterListOpen, setIsChapterListOpen] = useState(false); const [zoomLevel, setZoomLevel] = useState(1); const [isLiked, setIsLiked] = useState(false); const [darkMode, setDarkMode] = useState(true); useEffect(() => { if (!router.isReady || !comicId || !chapterId) return; const fetchData = async () => { try { const detailSnap = await getDocs(collection(db, "comics", comicId, "detailKomik")); if (!detailSnap.empty) { const detailId = detailSnap.docs[0].id; setDetailKomikId(detailId); const chapterSnap = await getDocs( collection(db, "comics", comicId, "detailKomik", detailId, "chapters") ); const chapters = chapterSnap.docs.map(doc => ({ id: doc.id, ...doc.data(), })).sort((a, b) => a.timestamp - b.timestamp); setChapterList(chapters); const index = chapters.findIndex(ch => ch.id === chapterId); setCurrentIndex(index); if (index !== -1) { setChapter(chapters[index]); } } } catch (err) { console.error("Error:", err); } }; fetchData(); }, [router.isReady, comicId, chapterId]); // Menemukan chapter sebelumnya dan berikutnya const prevChapter = currentIndex > 0 ? chapterList[currentIndex - 1] : null; const nextChapter = currentIndex < chapterList.length - 1 ? chapterList[currentIndex + 1] : null; // UI Control Functions const handleChapterChange = (e) => { const selectedId = e.target.value; if (selectedId !== chapterId) { router.push(/comic/${comicId}/${selectedId}); } }; const toggleMenu = () => { setIsMenuOpen(!isMenuOpen); if (isChapterListOpen) setIsChapterListOpen(false); }; const toggleChapterList = () => { setIsChapterListOpen(!isChapterListOpen); }; const toggleDarkMode = () => { setDarkMode(!darkMode); }; const toggleLike = () => { setIsLiked(!isLiked); }; const zoomIn = () => { if (zoomLevel < 2) { setZoomLevel(zoomLevel + 0.1); } }; const zoomOut = () => { if (zoomLevel > 0.5) { setZoomLevel(zoomLevel - 0.1); } }; const scrollToTop = () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }; const scrollToBottom = () => { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); }; const goToComicDetails = () => { router.push(/comic/${comicId}); }; const shareComic = () => { if (navigator.share) { navigator.share({ title: chapter?.title || 'Comic Chapter', text: Check out this comic chapter: ${chapter?.title}, url: window.location.href, }).catch(err => console.error('Error sharing:', err)); } else { alert(Share link copied: ${window.location.href}); // Fallback for browsers that don't support Web Share API navigator.clipboard.writeText(window.location.href); } }; // Styling variables based on dark mode const bgColor = darkMode ? 'bg-gray-900' : 'bg-gray-100'; const textColor = darkMode ? 'text-white' : 'text-gray-900'; const headerBg = darkMode ? 'bg-gray-800' : 'bg-white'; const cardBg = darkMode ? 'bg-gray-800' : 'bg-white'; const hoverBg = darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-200'; const borderColor = darkMode ? 'border-gray-700' : 'border-gray-300'; const buttonBg = darkMode ? 'bg-gray-800' : 'bg-white'; const accentColor = 'text-blue-500'; const accentBgColor = 'bg-blue-600'; const accentHoverColor = 'hover:bg-blue-700'; const menuBg = darkMode ? 'bg-gray-800' : 'bg-white'; // Set body background based on dark mode useEffect(() => { document.body.className = darkMode ? 'bg-gray-900' : 'bg-gray-100'; }, [darkMode]); if (!chapter) return ( <div className={${bgColor} ${textColor} min-h-screen flex items-center justify-center}> <div className="animate-pulse flex flex-col items-center"> <div className="h-8 w-32 bg-gray-600 rounded mb-4"></div> <p className="text-center p-4">Loading...</p> </div> </div> ); const { title, url, content } = chapter; return ( <div className={${bgColor} ${textColor} min-h-screen flex flex-col relative transition-colors duration-300}> {/* Header */} <header className={${headerBg} p-4 flex justify-between items-center sticky top-0 z-10 shadow-lg border-b ${borderColor} transition-colors duration-300}> <div className="flex items-center gap-2"> <h1 className="text-xl font-bold truncate max-w-[200px] sm:max-w-xs"> {title || Chapter ${currentIndex + 1}} </h1> </div> <div className="flex items-center gap-3"> <button onClick={toggleChapterList} className={p-2 rounded-full ${hoverBg} flex items-center gap-1 transition-colors duration-200} > <List size={20} /> <span className="hidden sm:inline">Chapters</span> </button> <button onClick={toggleLike} className={p-2 rounded-full ${hoverBg} hidden sm:flex transition-colors duration-200} aria-label={isLiked ? "Unlike chapter" : "Like chapter"} > <Heart size={20} fill={isLiked ? "#ef4444" : "none"} stroke={isLiked ? "#ef4444" : "currentColor"} /> </button> <button onClick={shareComic} className={p-2 rounded-full ${hoverBg} hidden sm:flex transition-colors duration-200} aria-label="Share" > <Share2 size={20} /> </button> <button onClick={toggleDarkMode} className={p-2 rounded-full ${hoverBg} hidden sm:flex transition-colors duration-200} aria-label={darkMode ? "Switch to light mode" : "Switch to dark mode"} > {darkMode ? ( <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> ) : ( <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path> </svg> )} </button> <div className="md:hidden"> <button onClick={toggleMenu} className={p-2 rounded-full ${hoverBg} transition-colors duration-200} aria-label="Open menu" > <Menu size={24} /> </button> </div> </div> <div className="hidden md:flex items-center gap-4"> <div className={flex items-center gap-2 px-3 py-1.5 rounded-full ${darkMode ? 'bg-gray-700' : 'bg-gray-200'} transition-colors duration-300}> <button onClick={zoomOut} className={p-1 rounded-full ${hoverBg} transition-colors duration-200}> <ZoomOut size={18} /> </button> <span className="text-sm font-medium">{Math.round(zoomLevel * 100)}%</span> <button onClick={zoomIn} className={p-1 rounded-full ${hoverBg} transition-colors duration-200}> <ZoomIn size={18} /> </button> </div> <div className={px-3 py-1.5 rounded-full ${darkMode ? 'bg-gray-700' : 'bg-gray-200'} text-sm font-medium transition-colors duration-300}> {currentIndex + 1}/{chapterList.length} </div> </div> </header> {/* Chapter Dropdown */} {isChapterListOpen && ( <div className={absolute top-16 right-4 z-50 ${menuBg} rounded-lg shadow-xl max-h-96 overflow-y-auto w-72 border ${borderColor} transition-colors duration-300}> <div className={p-3 border-b ${borderColor} flex justify-between items-center sticky top-0 ${menuBg} transition-colors duration-300}> <h3 className="font-bold">All Chapters</h3> <button onClick={toggleChapterList} className={p-1 ${hoverBg} rounded-full transition-colors duration-200}> <X size={18} /> </button> </div> <ul className="p-2"> {chapterList.map((ch, idx) => ( <li key={ch.id} className="mb-1"> <Link href={/comic/${comicId}/${ch.id}} legacyBehavior> <a className={block text-left p-2 rounded ${hoverBg} transition-colors duration-200 ${ch.id === chapterId ? (darkMode ? 'bg-gray-700' : 'bg-gray-200') : ''}}> <div className="flex justify-between"> <span>Chapter {idx + 1}</span> {ch.id === chapterId && ( <span className="text-blue-500 text-sm">Current</span> )} </div> <p className={text-sm ${darkMode ? 'text-gray-400' : 'text-gray-500'} truncate transition-colors duration-300}> {ch.title || Chapter ${idx + 1}} </p> </a> </Link> </li> ))} </ul> </div> )} {/* Mobile Menu */} {isMenuOpen && ( <div className="fixed inset-0 bg-black bg-opacity-80 backdrop-blur-sm z-50 flex flex-col md:hidden transition-colors duration-300"> <div className="p-4 flex justify-between items-center"> <h2 className="text-xl font-bold truncate max-w-[200px]">{title || Chapter ${currentIndex + 1}}</h2> <button onClick={toggleMenu} className={p-2 rounded-full ${hoverBg} transition-colors duration-200}> <X size={24} /> </button> </div> <div className="p-4 flex flex-col gap-4"> <div className={flex items-center justify-center gap-4 p-3 rounded-lg ${menuBg} shadow transition-colors duration-300}> <button onClick={zoomOut} className={p-2 rounded-full ${hoverBg} transition-colors duration-200}> <ZoomOut size={24} /> </button> <span className="text-lg font-medium">{Math.round(zoomLevel * 100)}%</span> <button onClick={zoomIn} className={p-2 rounded-full ${hoverBg} transition-colors duration-200}> <ZoomIn size={24} /> </button> </div> <div className={text-center text-lg p-3 rounded-lg ${menuBg} shadow transition-colors duration-300}> Chapter {currentIndex + 1} of {chapterList.length} </div> <div className="grid grid-cols-2 gap-4 mt-2"> <button onClick={scrollToTop} className={flex items-center justify-center gap-2 p-3 ${menuBg} rounded-lg ${hoverBg} shadow transition-colors duration-200} > <ChevronUp size={20} /> <span>Scroll Up</span> </button> <button onClick={scrollToBottom} className={flex items-center justify-center gap-2 p-3 ${menuBg} rounded-lg ${hoverBg} shadow transition-colors duration-200} > <ChevronDown size={20} /> <span>Scroll Down</span> </button> </div> <div className="grid grid-cols-2 gap-4"> <button onClick={toggleLike} className={flex items-center justify-center gap-2 p-3 ${menuBg} rounded-lg ${hoverBg} shadow transition-colors duration-200} > <Heart size={20} fill={isLiked ? "#ef4444" : "none"} stroke={isLiked ? "#ef4444" : "currentColor"} /> <span>{isLiked ? "Liked" : "Like"}</span> </button> <button onClick={shareComic} className={flex items-center justify-center gap-2 p-3 ${menuBg} rounded-lg ${hoverBg} shadow transition-colors duration-200} > <Share2 size={20} /> <span>Share</span> </button> </div> <button onClick={toggleDarkMode} className={flex items-center justify-center gap-2 p-3 ${menuBg} rounded-lg ${hoverBg} shadow transition-colors duration-200} > {darkMode ? ( <> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> <span>Light Mode</span> </> ) : ( <> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path> </svg> <span>Dark Mode</span> </> )} </button> {/* Chapter selector for mobile */} <div className={p-3 ${menuBg} rounded-lg shadow transition-colors duration-300}> <label htmlFor="mobileChapterSelect" className="block text-sm font-medium mb-2">📚 Pilih Chapter</label> <select id="mobileChapterSelect" value={chapterId} onChange={handleChapterChange} className={w-full p-2 rounded-lg ${darkMode ? 'bg-gray-700 text-white' : 'bg-white text-gray-900'} border ${borderColor}} > {chapterList.map((ch, idx) => ( <option key={ch.id} value={ch.id}> {ch.title || Chapter ${idx + 1}} </option> ))} </select> </div> </div> </div> )} {/* Main Content */} <main className="flex-1 flex flex-col items-center justify-center max-w-5xl mx-auto w-full"> {/* Chapter Select - Desktop */} <div className={hidden md:block mb-6 w-full ${cardBg} rounded-lg shadow-lg p-4 m-6 border ${borderColor}}> <div className="flex items-center gap-4 "> <label htmlFor="chapterSelect" className="font-medium flex items-center gap-1"> <List size={16} /> Pilih Chapter </label> <select id="chapterSelect" value={chapterId} onChange={handleChapterChange} className={flex-1 p-2 m-4 rounded-lg ${darkMode ? 'bg-gray-700 text-white' : 'bg-white text-gray-900'} border ${borderColor}} > {chapterList.map((ch, idx) => ( <option key={ch.id} value={ch.id}> {ch.title || Chapter ${idx + 1}} </option> ))} </select> </div> </div> {/* Comic View */} <div className="w-full flex items-center justify-center relative "> <div className={shadow-2xl transition-transform overflow-hidden rounded-lg ${darkMode ? '' : 'bg-white'}} style={{ transform: scale(${zoomLevel}), maxWidth: '100%', transition: 'transform 0.3s ease' }} > <img src={url} alt={title} className="max-w-full h-auto object-contain rounded-lg" /> </div> </div> {/* Content Description (if any) */} {content && ( <div className={w-full ${cardBg} rounded-lg shadow-lg p-4 my-6 border ${borderColor}}> <h2 className="text-xl font-bold mb-2">Deskripsi</h2> <div className={${darkMode ? 'text-gray-300' : 'text-gray-700'}}> <p>{content}</p> </div> </div> )} </main> {/* Chapter Navigation */} <div className={${headerBg} border-t ${borderColor} p-4 w-full transition-colors duration-300}> <div className="flex justify-between items-center max-w-5xl mx-auto"> {prevChapter ? ( <Link href={/comic/${comicId}/${prevChapter.id}} legacyBehavior> <a className={flex items-center gap-2 px-4 py-2 rounded-lg ${accentBgColor} ${accentHoverColor} text-white font-medium transition-colors duration-200}> <ChevronLeft size={20} /> <span className="hidden sm:inline">Chapter Sebelumnya</span> <span className="sm:hidden">Sebelumnya</span> </a> </Link> ) : ( <div className="px-4 py-2 rounded-lg bg-gray-600 opacity-50 cursor-not-allowed text-white font-medium"> <span className="flex items-center gap-2"> <ChevronLeft size={20} /> <span className="hidden sm:inline">Chapter Sebelumnya</span> <span className="sm:hidden">Sebelumnya</span> </span> </div> )} <div className="flex gap-3"> <button onClick={goToComicDetails} className={p-2 rounded-lg ${buttonBg} shadow ${hoverBg} transition-colors duration-200} aria-label="Go to comic details" > <Home size={20} /> </button> <button onClick={toggleChapterList} className={p-2 rounded-lg ${buttonBg} shadow ${hoverBg} transition-colors duration-200} aria-label="Open chapter list" > <List size={20} /> </button> </div> {nextChapter ? ( <Link href={/comic/${comicId}/${nextChapter.id}} legacyBehavior> <a className={flex items-center gap-2 px-4 py-2 rounded-lg ${accentBgColor} ${accentHoverColor} text-white font-medium transition-colors duration-200}> <span className="hidden sm:inline">Chapter Selanjutnya</span> <span className="sm:hidden">Selanjutnya</span> <ChevronRight size={20} /> </a> </Link> ) : ( <div className="px-4 py-2 rounded-lg bg-gray-600 opacity-50 cursor-not-allowed text-white font-medium"> <span className="flex items-center gap-2"> <span className="hidden sm:inline">Chapter Selanjutnya</span> <span className="sm:hidden">Selanjutnya</span> <ChevronRight size={20} /> </span> </div> )} </div> </div> {/* Floating Action Buttons */} <div className="fixed bottom-20 right-4 flex flex-col gap-2 z-30"> <button onClick={scrollToTop} className={p-3 ${buttonBg} rounded-full shadow-xl ${hoverBg} transition-colors duration-200} aria-label="Scroll to top" > <ChevronUp size={24} /> </button> <button onClick={scrollToBottom} className={p-3 ${buttonBg} rounded-full shadow-xl ${hoverBg} transition-colors duration-200} aria-label="Scroll to bottom" > <ChevronDown size={24} /> </button> </div> {/* Comic Details Button - Fixed at bottom right */} <button onClick={goToComicDetails} className={fixed bottom-4 right-4 p-3 ${accentBgColor} rounded-full shadow-xl ${accentHoverColor} z-30 text-white transition-colors duration-200} aria-label="Go to comic details" > <BookOpen size={24} /> </button> </div> ); }
Please keep input under 1000 characters