'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 (
}
/>
) : (
)}
);
}
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 (
);
}
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) => (
))}
);
}