'use client'; import { useState, useEffect, useRef, useCallback } from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; import { ZoomIn, ZoomOut, Loader2 } from 'lucide-react'; import { useInView } from 'react-intersection-observer'; import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/TextLayer.css'; if (typeof window !== 'undefined') { pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`; } function LazyPage({ pageNumber, scale, containerWidth }: { pageNumber: number; scale: number; containerWidth: number }) { const { ref, inView } = useInView({ triggerOnce: false, rootMargin: '150% 0px', }); // Estimate the page height based on typical A4 ratio (1:1.414) const estimatedWidth = Math.min(containerWidth * 0.9, 900) * scale; const estimatedHeight = estimatedWidth * 1.414; return (
{inView ? (
} /> ) : (
)}
); } export default function PdfViewer({ url }: { url: string }) { const [numPages, setNumPages] = useState(); const [scale, setScale] = useState(1.0); const [blobUrl, setBlobUrl] = useState(null); const [fetchError, setFetchError] = useState(null); const [fetchingPdf, setFetchingPdf] = useState(true); const containerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(800); // Measure container width for responsive page sizing useEffect(() => { const el = containerRef.current; if (!el) return; const ro = new ResizeObserver(([entry]) => { setContainerWidth(entry.contentRect.width); }); ro.observe(el); return () => ro.disconnect(); }, []); // Fetch the PDF as a blob to avoid Next.js proxy streaming issues with large files useEffect(() => { let cancelled = false; setFetchingPdf(true); setFetchError(null); fetch(url) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.blob(); }) .then((blob) => { if (cancelled) return; const objectUrl = URL.createObjectURL(blob); setBlobUrl(objectUrl); }) .catch((err) => { if (cancelled) return; setFetchError(`Failed to load PDF: ${err.message}`); }) .finally(() => { if (!cancelled) setFetchingPdf(false); }); return () => { cancelled = true; if (blobUrl) URL.revokeObjectURL(blobUrl); }; }, [url]); // eslint-disable-line react-hooks/exhaustive-deps function onDocumentLoadSuccess({ numPages }: { numPages: number }): void { setNumPages(numPages); } if (fetchingPdf) { return (
Loading PDF...
); } if (fetchError) { return (
{fetchError} Download Instead
); } return (
{/* PDF Toolbar */}
{numPages ? `${numPages} page${numPages !== 1 ? 's' : ''}` : 'Loading...'}
{Math.round(scale * 100)}%
{/* PDF Content */}
console.error('Failed to load PDF:', error)} loading={
} className="flex flex-col items-center gap-2 w-full" > {numPages && Array.from(new Array(numPages), (_, index) => ( ))}
); }