espace-paie-odentas/app/signer/[requestId]/[signerId]/components/PDFViewer.tsx
odentas b790faf12c feat: Implémentation complète du système Odentas Sign
- Remplacement de DocuSeal par solution souveraine Odentas Sign
- Système d'authentification OTP pour signataires (bcryptjs + JWT)
- 8 routes API: send-otp, verify-otp, sign, pdf-url, positions, status, webhook, signers
- Interface moderne avec canvas de signature et animations (framer-motion, confetti)
- Système de templates pour auto-détection des positions de signature (CDDU, RG, avenants)
- PDF viewer avec @react-pdf-viewer (compatible Next.js)
- Stockage S3: source/, signatures/, evidence/, signed/, certs/
- Tables Supabase: sign_requests, signers, sign_positions, sign_events, sign_assets
- Evidence bundle automatique (JSON metadata + timestamps)
- Templates emails: OTP et completion
- Scripts Lambda prêts: pades-sign (KMS seal) et tsaStamp (RFC3161)
- Mode test détecté automatiquement (emails whitelist)
- Tests complets avec PDF CDDU réel (2 signataires)
2025-10-27 19:03:07 +01:00

84 lines
No EOL
2.7 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { Viewer, Worker } from '@react-pdf-viewer/core';
import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout';
// Import des styles
import '@react-pdf-viewer/core/lib/styles/index.css';
import '@react-pdf-viewer/default-layout/lib/styles/index.css';
interface SignaturePosition {
page: number;
x: number;
y: number;
width: number;
height: number;
role: string;
}
interface PDFViewerProps {
pdfUrl: string;
positions: SignaturePosition[];
currentSignerRole: string;
}
export default function PDFViewer({ pdfUrl, positions, currentSignerRole }: PDFViewerProps) {
const [mounted, setMounted] = useState(false);
// Plugin pour la mise en page par défaut
const defaultLayoutPluginInstance = defaultLayoutPlugin();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return (
<div className="flex items-center justify-center h-full bg-gray-50 rounded-lg border border-gray-200">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Initialisation du viewer...</p>
</div>
</div>
);
}
return (
<div className="h-full bg-gray-50 rounded-lg overflow-hidden border border-gray-200">
<Worker workerUrl={`https://unpkg.com/pdfjs-dist@3.11.174/build/pdf.worker.min.js`}>
<div className="relative h-full">
<Viewer
fileUrl={pdfUrl}
plugins={[defaultLayoutPluginInstance]}
defaultScale={1.2}
/>
{/* Overlay custom pour les zones de signature */}
<div className="absolute top-16 right-4 bg-white/95 backdrop-blur-sm p-3 rounded-lg shadow-lg border border-gray-200">
<h4 className="text-xs font-semibold text-gray-700 mb-2">Zones de signature</h4>
<div className="space-y-2">
{positions.map((pos, index) => {
const isCurrentSigner = pos.role === currentSignerRole;
return (
<div
key={index}
className={`flex items-center gap-2 text-xs ${
isCurrentSigner ? 'text-blue-600 font-semibold' : 'text-gray-600'
}`}
>
{isCurrentSigner ? '✍️' : '📝'}
<span>Page {pos.page}: {pos.role}</span>
</div>
);
})}
</div>
{positions.length === 0 && (
<p className="text-xs text-gray-500 italic">Aucune zone définie</p>
)}
</div>
</div>
</Worker>
</div>
);
}