Reuben_OS / app /components /PDFViewer.tsx
Reubencf's picture
First Push
8af739b
'use client'
import React, { useState } from 'react'
import dynamic from 'next/dynamic'
import { MagnifyingGlassPlus, MagnifyingGlassMinus, ArrowLeft, ArrowRight } from '@phosphor-icons/react'
// Dynamically import react-pdf with no SSR
const Document = dynamic(
() => import('react-pdf').then((mod) => mod.Document),
{ ssr: false }
)
const Page = dynamic(
() => import('react-pdf').then((mod) => mod.Page),
{ ssr: false }
)
// Configure PDF.js worker
if (typeof window !== 'undefined') {
import('react-pdf').then((pdfjs) => {
pdfjs.pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.pdfjs.version}/build/pdf.worker.min.mjs`
})
}
interface PDFViewerProps {
url: string
scale: number
onZoomIn: () => void
onZoomOut: () => void
}
export function PDFViewer({ url, scale, onZoomIn, onZoomOut }: PDFViewerProps) {
const [numPages, setNumPages] = useState<number | null>(null)
const [pageNumber, setPageNumber] = useState(1)
const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(true)
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
setNumPages(numPages)
setPageNumber(1)
setError(null)
setIsLoading(false)
}
const changePage = (offset: number) => {
setPageNumber(prevPageNumber => {
const newPageNumber = prevPageNumber + offset
if (newPageNumber < 1 || newPageNumber > (numPages || 1)) {
return prevPageNumber
}
return newPageNumber
})
}
if (!url) {
return <div className="flex items-center justify-center h-full text-gray-500">No PDF to display</div>
}
return (
<div className="flex flex-col h-full">
<div className="flex items-center gap-2 p-3 border-b border-gray-200 bg-gray-50">
<button
onClick={onZoomOut}
className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
title="Zoom Out"
>
<MagnifyingGlassMinus className="h-5 w-5" />
</button>
<span className="text-sm text-gray-600 min-w-[60px] text-center">{Math.round(scale * 100)}%</span>
<button
onClick={onZoomIn}
className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
title="Zoom In"
>
<MagnifyingGlassPlus className="h-5 w-5" />
</button>
{numPages && (
<>
<div className="mx-4 h-6 w-px bg-gray-300" />
<button
onClick={() => changePage(-1)}
disabled={pageNumber <= 1}
className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="Previous Page"
>
<ArrowLeft className="h-5 w-5" />
</button>
<span className="text-sm text-gray-600 min-w-[80px] text-center">
Page {pageNumber} of {numPages}
</span>
<button
onClick={() => changePage(1)}
disabled={pageNumber >= numPages}
className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="Next Page"
>
<ArrowRight className="h-5 w-5" />
</button>
</>
)}
</div>
<div className="flex-1 overflow-auto p-4 bg-gray-50">
{typeof window !== 'undefined' && url && (
<Document
file={url}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={(err) => {
console.warn('PDF load error:', err)
setError('Unable to load PDF. The file might be corrupted or in an unsupported format.')
setIsLoading(false)
}}
loading={
<div className="flex items-center justify-center h-full">
<div className="text-gray-600">Loading PDF...</div>
</div>
}
error={
<div className="flex flex-col items-center justify-center h-full">
<div className="text-red-500 mb-2">Failed to load PDF</div>
<div className="text-sm text-gray-600">{error || 'The file might be corrupted or in an unsupported format.'}</div>
</div>
}
className="flex justify-center"
>
{!isLoading && !error && (
<Page
pageNumber={pageNumber}
scale={scale}
renderTextLayer={false}
renderAnnotationLayer={false}
className="shadow-lg bg-white"
loading={
<div className="flex items-center justify-center h-96 bg-white shadow-lg">
<div className="text-gray-400">Loading page...</div>
</div>
}
/>
)}
</Document>
)}
</div>
</div>
)
}