()
| 20 | }; |
| 21 | |
| 22 | const DiscoverPage: React.FC = () => { |
| 23 | const [books, setBooks] = useState<Book[]>([]); |
| 24 | const [currentIndex, setCurrentIndex] = useState(() => { |
| 25 | try { |
| 26 | const saved = localStorage.getItem('olib-discover-current-index'); |
| 27 | return saved ? Math.max(0, parseInt(saved, 10) || 0) : 0; |
| 28 | } catch { return 0; } |
| 29 | }); |
| 30 | const [loading, setLoading] = useState(false); |
| 31 | const [error, setError] = useState(''); |
| 32 | const [errorIsLine, setErrorIsLine] = useState(false); |
| 33 | const [downloadingIds, setDownloadingIds] = useState<Set<string>>(new Set()); |
| 34 | const [limitError, setLimitError] = useState<string | null>(null); |
| 35 | const [selectedBook, setSelectedBook] = useState<any>(null); |
| 36 | const [loadingDetail, setLoadingDetail] = useState(false); |
| 37 | const [viewMode, setViewMode] = useState<'shelf' | 'grid'>('shelf'); |
| 38 | |
| 39 | const { getCoverUrl, handleCoverError } = useCoverCache(); |
| 40 | |
| 41 | const currentIndexRef = useRef(currentIndex); |
| 42 | currentIndexRef.current = currentIndex; |
| 43 | const booksRef = useRef(books); |
| 44 | booksRef.current = books; |
| 45 | const viewModeRef = useRef(viewMode); |
| 46 | viewModeRef.current = viewMode; |
| 47 | |
| 48 | // Restore saved view mode |
| 49 | useEffect(() => { |
| 50 | try { |
| 51 | const savedMode = localStorage.getItem('olib-discover-view-mode'); |
| 52 | if (savedMode === 'grid' || savedMode === 'shelf') setViewMode(savedMode); |
| 53 | // Migrate old 'tinder' preference |
| 54 | if (savedMode === 'tinder') { |
| 55 | setViewMode('shelf'); |
| 56 | localStorage.setItem('olib-discover-view-mode', 'shelf'); |
| 57 | } |
| 58 | } catch { /* ignore */ } |
| 59 | }, []); |
| 60 | |
| 61 | const handleViewModeChange = (mode: 'shelf' | 'grid') => { |
| 62 | setViewMode(mode); |
| 63 | try { localStorage.setItem('olib-discover-view-mode', mode); } catch { /* ignore */ } |
| 64 | }; |
| 65 | |
| 66 | const quote = useMemo( |
| 67 | () => READING_QUOTES[Math.floor(Math.random() * READING_QUOTES.length)], |
| 68 | [] |
| 69 | ); |
| 70 | |
| 71 | useEffect(() => { loadBooks(); }, []); |
| 72 | |
| 73 | // Keyboard handler (simplified: ← → browse, Enter detail, F favorite) |
| 74 | useEffect(() => { |
| 75 | const handler = (e: KeyboardEvent) => { |
| 76 | if (selectedBook) return; |
| 77 | if (viewModeRef.current !== 'shelf') return; |
| 78 | |
| 79 | if (e.key === 'ArrowLeft') { |
nothing calls this directly
no test coverage detected