Preview com react-pdf
Renderize PDFs assinados e não assinados ao lado do signer e envie retângulos clicados para o workflow de PDF.
react-pdf pertence à sua app, não aos pacotes SignatureKit. Use-o como superfície de preview, mantenha object URLs na ação que os cria e passe o retângulo selecionado para prepareAndSignPdf.
import type { PdfPreviewProps } from "./PdfPreview"
import { lazy, Suspense, useSyncExternalStore } from "react"
const LazyPdfPreview = lazy(() =>
import("./PdfPreview").then((module) => ({ default: module.PdfPreview })),
)
const subscribe = (_onStoreChange: () => void) => () => {}
const getClientSnapshot = () => true
const getServerSnapshot = () => false
function useHydrated() {
return useSyncExternalStore(subscribe, getClientSnapshot, getServerSnapshot)
}
function PdfPreviewFallback() {
return <p role="status" aria-live="polite">Carregando preview do PDF…</p>
}
export function PdfPreviewIsland(props: PdfPreviewProps) {
const hydrated = useHydrated()
return (
<Suspense fallback={<PdfPreviewFallback />}>
{hydrated ? <LazyPdfPreview {...props} /> : <PdfPreviewFallback />}
</Suspense>
)
}PdfPreviewIsland é o boundary cross-framework: o Waku renderiza o snapshot do servidor como false, então o callback lazy (e o módulo react-pdf/pdf.js que ele carrega) nunca é avaliado durante SSR ou na passagem inicial de hidratação. Quando o snapshot do navegador vira true, Suspense mantém o fallback visível até o chunk do cliente ser resolvido. O mesmo contrato React funciona em outros frameworks SSR sem um helper dynamic específico de framework.
import { type PdfSignaturePage, type PdfSignatureRect } from "@signature-kit/pdf/config"
import { Document, Page } from "react-pdf"
import * as React from "react"
const STAMP_SIZE = { width: 180, height: 54 }
export type PdfPreviewProps = {
readonly unsignedPdf: Uint8Array
readonly pages: Array<PdfSignaturePage>
readonly sign: (rect: PdfSignatureRect) => Promise<Uint8Array>
}
export function PdfPreview({ unsignedPdf, pages, sign }: PdfPreviewProps) {
const [previewUrl, setPreviewUrl] = React.useState<string>()
const [stampRect, setStampRect] = React.useState<PdfSignatureRect>()
const showBytes = React.useCallback((bytes: Uint8Array) => {
setPreviewUrl((current) => {
if (current !== undefined) URL.revokeObjectURL(current)
return URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }))
})
}, [])
const clearPreview = React.useCallback(() => {
setPreviewUrl((current) => {
if (current !== undefined) URL.revokeObjectURL(current)
return undefined
})
}, [])
const placeOnClick = React.useCallback(
(page: PdfSignaturePage, event: React.MouseEvent<HTMLDivElement>) => {
const bounds = event.currentTarget.getBoundingClientRect()
const x = ((event.clientX - bounds.left) / bounds.width) * page.width
const y = ((event.clientY - bounds.top) / bounds.height) * page.height
setStampRect({
pageIndex: page.index,
x: Math.min(Math.max(0, x), page.width - STAMP_SIZE.width),
y: Math.min(Math.max(0, y), page.height - STAMP_SIZE.height),
width: STAMP_SIZE.width,
height: STAMP_SIZE.height,
})
},
[],
)
const signPlaced = React.useCallback(async () => {
if (stampRect === undefined) return
const signedPdf = await sign(stampRect)
showBytes(signedPdf)
}, [showBytes, sign, stampRect])
return (
<section>
<button type="button" onClick={() => showBytes(unsignedPdf)}>Preview sem assinatura</button>
<button type="button" onClick={signPlaced}>Assinar retângulo</button>
<button type="button" onClick={clearPreview}>Fechar preview</button>
{previewUrl === undefined ? null : (
<Document file={previewUrl}>
{pages.map((page) => (
<div key={page.index} onClick={(event) => placeOnClick(page, event)}>
<Page pageNumber={page.index + 1} width={720} />
</div>
))}
</Document>
)}
</section>
)
}import { a1SignaturesLayer } from "@signature-kit/a1/signer"
import { type PdfSignatureRect } from "@signature-kit/pdf/config"
import { prepareAndSignPdf } from "@signature-kit/pdf/workflow"
import { Effect, Layer, Redacted } from "effect"
const sign = (rect: PdfSignatureRect): Promise<Uint8Array> =>
Effect.runPromise(
prepareAndSignPdf({
pdf: unsignedPdf,
pages,
stampRects: [rect],
lines: ["Maria Souza", "ICP-Brasil"],
rubric: { initials: "MS" },
signing: { policy: "pades-icp-brasil", name: "Maria Souza" },
}).pipe(
Effect.provide(
Layer.merge(
a1SignaturesLayer({ pfx, password: Redacted.make(password) }),
liteParseWorkerBrowserLayer,
),
),
),
)A app controla preview e react-pdf; SignatureKit continua responsável por parsing, carimbos, rubricas e assinatura.
Como está este guia?